Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSVC C++20 Support #126

Merged
merged 20 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions adobe/algorithm/append.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2024 Adobe
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
/**************************************************************************************************/

#ifndef ADOBE_ALGORITHM_APPEND_HPP
#define ADOBE_ALGORITHM_APPEND_HPP

/**************************************************************************************************/

namespace adobe {

/**************************************************************************************************/

/// Insert the range `r` at the end of the sequence container `x`.
/// Returns an iterator pointing to the first element inserted, or `x.end()` if `r` is empty.
template <class T, // T models SequenceContainer
class R> // R models Range
inline auto append(T& x, const R& r) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Insert the range `r` at the end of the sequence container `x`.
/// Returns an iterator pointing to the first element inserted, or `x.end()` if `r` is empty.
template <class T, // T models SequenceContainer
class R> // R models Range
inline auto append(T& x, const R& r) {
```suggestion
/// Inserts the elements of *range* `r` (a `Range`) at the end of *forward container* `x`,
/// returning the position corresponding to the incoming `end` of `x`.
template <class T, class R>
inline auto append(T& x, const R& r) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted.

return x.insert(std::end(x), std::begin(r), std::end(r));
}

/**************************************************************************************************/

} // namespace adobe

/**************************************************************************************************/

#endif
6 changes: 3 additions & 3 deletions source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR
endif()

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_options (asl PRIVATE "/DNOMINMAX")
target_compile_options (asl PRIVATE "/permissive-")
target_compile_options (asl PRIVATE "/Zc:__cplusplus")
target_compile_options (asl PUBLIC "/DNOMINMAX")
target_compile_options (asl PUBLIC "/permissive-")
target_compile_options (asl PUBLIC "/Zc:__cplusplus")
target_compile_options (asl PRIVATE "/W4")
target_compile_options (asl PRIVATE "/WX")
endif()
Expand Down
179 changes: 81 additions & 98 deletions source/adam.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <utility>
#include <vector>

#include <adobe/algorithm/append.hpp>
#include <adobe/algorithm/find.hpp>
#include <adobe/algorithm/for_each.hpp>
#include <adobe/algorithm/sort.hpp>
Expand Down Expand Up @@ -345,6 +346,27 @@ class sheet_t::implementation_t : boost::noncopyable {
priority_t name_to_priority(name_t name) const;
void flow(cell_bits_t& priority_accessed);

/// Returns the output cell with the given name.
/// \pre the output cell must exist.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Returns the output cell with the given name.
/// \pre the output cell must exist.
/// Returns the output cell with the given name.
///
/// \pre A cell with that name exists.

Blank line after summary.
A precondition is a predicate statement. Write in English what you'd put in the assert() parens.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted.

cell_t& output_cell(const name_t& name) {
auto p = output_index_m.find(name);
assert(p != output_index_m.end() && "output cell not found");
return *p;
}
const cell_t& output_cell(const name_t& name) const {
auto p = output_index_m.find(name);
assert(p != output_index_m.end() && "output cell not found");
return *p;
}

/// Returns `true` if any output cells in the relation are resolved, false otherwise.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some people think iff is too obscure, but this is an option

Suggested change
/// Returns `true` if any output cells in the relation are resolved, false otherwise.
/// Returns `true` iff any output cells in the relation are resolved.

Some people like:

Suggested change
/// Returns `true` if any output cells in the relation are resolved, false otherwise.
/// Returns whether any output cells in the relation are resolved.

I find that really awkward personally but I can see the argument.

If you are in the context of a type representing a relation, you can leave out “in the relation.”

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference was iff (I had that initially, but I know people who would object, so I changed it). I'll go with "whether" -

bool resolved(const relation_t& relation) const {
return find_if(relation.name_set_m, [&](const auto& name) {
return output_cell(name).resolved_m;
}) != relation.name_set_m.end();
}


/*
NOTE (sparent) : cell_t contains boost::signals2::signal<> which is not copyable. The cells
support
Expand Down Expand Up @@ -717,10 +739,10 @@ void sheet_t::implementation_t::add_input(name_t name, const line_position_t& po
void sheet_t::implementation_t::add_output(name_t name, const line_position_t& position,
const array_t& expression) {
// REVISIT (sparent) : Non-transactional on failure.
cell_set_m.push_back(cell_t(access_output, name,
std::bind(&implementation_t::calculate_expression,
std::ref(*this), position, expression),
cell_set_m.size(), nullptr));
cell_set_m.push_back(cell_t(
access_output, name,
std::bind(&implementation_t::calculate_expression, std::ref(*this), position, expression),
cell_set_m.size(), nullptr));

output_index_m.insert(cell_set_m.back());

Expand All @@ -744,11 +766,10 @@ void sheet_t::implementation_t::add_interface(name_t name, bool linked,
scope_value_t<bool> scope(initialize_mode_m, true);

if (initializer_expression.size()) {
cell_set_m.push_back(
cell_t(name, linked,
std::bind(&implementation_t::calculate_expression, std::ref(*this),
position1, initializer_expression),
cell_set_m.size()));
cell_set_m.push_back(cell_t(name, linked,
std::bind(&implementation_t::calculate_expression,
std::ref(*this), position1, initializer_expression),
cell_set_m.size()));
} else {
cell_set_m.push_back(cell_t(name, linked, cell_t::calculator_t(), cell_set_m.size()));
}
Expand All @@ -763,7 +784,7 @@ void sheet_t::implementation_t::add_interface(name_t name, bool linked,
// REVISIT (sparent) : Non-transactional on failure.
cell_set_m.push_back(cell_t(access_interface_output, name,
std::bind(&implementation_t::calculate_expression,
std::ref(*this), position2, expression),
std::ref(*this), position2, expression),
cell_set_m.size(), &cell_set_m.back()));
} else {
cell_set_m.push_back(cell_t(access_interface_output, name,
Expand Down Expand Up @@ -831,10 +852,10 @@ void sheet_t::implementation_t::add_constant(name_t name, any_regular_t value) {

void sheet_t::implementation_t::add_logic(name_t logic, const line_position_t& position,
const array_t& expression) {
cell_set_m.push_back(cell_t(access_logic, logic,
std::bind(&implementation_t::calculate_expression,
std::ref(*this), position, expression),
cell_set_m.size(), nullptr));
cell_set_m.push_back(cell_t(
access_logic, logic,
std::bind(&implementation_t::calculate_expression, std::ref(*this), position, expression),
cell_set_m.size(), nullptr));

if (!name_index_m.insert(cell_set_m.back()).second) {
throw stream_error_t(make_string("cell named '", logic.c_str(), "'already exists."),
Expand All @@ -847,10 +868,10 @@ void sheet_t::implementation_t::add_logic(name_t logic, const line_position_t& p
void sheet_t::implementation_t::add_invariant(name_t name, const line_position_t& position,
const array_t& expression) {
// REVISIT (sparent) : Non-transactional on failure.
cell_set_m.push_back(cell_t(access_invariant, name,
std::bind(&implementation_t::calculate_expression,
std::ref(*this), position, expression),
cell_set_m.size(), nullptr));
cell_set_m.push_back(cell_t(
access_invariant, name,
std::bind(&implementation_t::calculate_expression, std::ref(*this), position, expression),
cell_set_m.size(), nullptr));

output_index_m.insert(cell_set_m.back());

Expand Down Expand Up @@ -936,7 +957,7 @@ sheet_t::connection_t sheet_t::implementation_t::monitor_enabled(name_t n, const
(touch_set & priority_accessed_m).any()));

return monitor_enabled_m.connect(std::bind(&sheet_t::implementation_t::enabled_filter, this,
touch_set, iter->cell_set_pos_m, monitor, _1, _2));
touch_set, iter->cell_set_pos_m, monitor, _1, _2));
}

/**************************************************************************************************/
Expand Down Expand Up @@ -988,8 +1009,8 @@ sheet_t::implementation_t::monitor_contributing(name_t n, const dictionary_t& ma
monitor(contributing_set(mark, iter->contributing_m));

return iter->monitor_contributing_m.connect(
std::bind(monitor, std::bind(&sheet_t::implementation_t::contributing_set,
std::ref(*this), mark, _1)));
std::bind(monitor, std::bind(&sheet_t::implementation_t::contributing_set, std::ref(*this),
mark, _1)));
}

/**************************************************************************************************/
Expand Down Expand Up @@ -1018,10 +1039,9 @@ priority_t sheet_t::implementation_t::name_to_priority(name_t name) const {
void sheet_t::implementation_t::flow(cell_bits_t& priority_accessed) {
// Generate the set of cells connected to unresolved relations
vector<cell_t*> cells;
for (relation_cell_set_t::iterator f(relation_cell_set_m.begin()), l(relation_cell_set_m.end());
f != l; ++f) {
if (!f->resolved_m)
cells.insert(cells.end(), f->edges_m.begin(), f->edges_m.end());
for (const auto& relation : relation_cell_set_m) {
if (!relation.resolved_m)
append(cells, relation.edges_m);
}
sort(cells);
cells.erase(unique(cells), cells.end());
Expand All @@ -1033,8 +1053,8 @@ void sheet_t::implementation_t::flow(cell_bits_t& priority_accessed) {
// REVISIT <seanparent@google.com> : This is an approximation for enablement that could do
// better with connected components

for (vector<cell_t*>::iterator f = cells.begin(), l = cells.end(); f != l; ++f) {
priority_accessed.set((*f)->interface_input_m->cell_set_pos_m);
for (const auto& cell: cells) {
priority_accessed.set(cell->interface_input_m->cell_set_pos_m);
}

/*
Expand All @@ -1057,120 +1077,83 @@ void sheet_t::implementation_t::flow(cell_bits_t& priority_accessed) {

cell.resolved_m = true;

for (relation_index_t::iterator f = cell.relation_index_m.begin(),
l = cell.relation_index_m.end();
f != l; ++f) {

if ((*f)->resolved_m)
for (const auto& relation : cell.relation_index_m) {
if (relation->resolved_m)
continue;

--cell.relation_count_m;

const relation_t* term = 0;
bool at_least_one = false;

for (relation_set_t::iterator tf((*f)->terms_m.begin()), tl((*f)->terms_m.end());
tf != tl; ++tf) {
// each term has a set of cells. If any cell is resolved then the term is resolved.

bool resolved = false;
for (vector<name_t>::iterator fc = tf->name_set_m.begin(),
lc = tf->name_set_m.end();
fc != lc; ++fc) {

index_t::iterator iter = output_index_m.find(*fc);
assert(iter != output_index_m.end());

if (iter->resolved_m) {
resolved = true;
break;
}
}
if (resolved)
continue;

if (!term) {
term = &(*tf);
at_least_one = true;
} else {
term = NULL;
break;
}
}
auto term =
find_if(relation->terms_m, [&](const auto& term) { return !resolved(term); });

// REVISIT (sparent) : Better error reporting here.
if (!at_least_one) {
if (term == relation->terms_m.end())
throw std::logic_error("all terms of relation resolve but relation not applied.");
}

if (!term)
// If there is more than one unresolved term, continue.
if (std::find_if(next(term), relation->terms_m.end(), [&](const auto& term) {
return !resolved(term);
}) != relation->terms_m.end())
continue;

// Flow out to lhs cells

(*f)->resolved_m = true;
relation->resolved_m = true;

for (std::size_t n = 0, count = term->name_set_m.size(); n != count; ++n) {
index_t::iterator iter = output_index_m.find(term->name_set_m[n]);
assert(iter != output_index_m.end());

cell_t& e = *iter;
auto& out_cell = output_cell(term->name_set_m[n]);

// REVISIT (sparent) : Better error reporting here.
if (e.term_m)
if (out_cell.term_m)
throw logic_error("over constrained.");

if (count == 1) {
e.term_m = std::bind(&implementation_t::calculate_expression, std::ref(*this),
term->position_m, term->expression_m);
out_cell.term_m =
std::bind(&implementation_t::calculate_expression, std::ref(*this),
term->position_m, term->expression_m);
} else {
e.term_m = std::bind(&implementation_t::calculate_indexed, std::ref(*this),
term->position_m, term->expression_m, n);
out_cell.term_m =
std::bind(&implementation_t::calculate_indexed, std::ref(*this),
term->position_m, term->expression_m, n);
}

--e.relation_count_m;
--out_cell.relation_count_m;

// This will be a derived cell and will have a priority lower than any cell
// contributing to it

assert(e.interface_input_m && "Missing input half of interface cell.");
if (e.interface_input_m->linked_m) {
e.interface_input_m->priority_m = --priority_low_m;
assert(out_cell.interface_input_m && "Missing input half of interface cell.");
if (out_cell.interface_input_m->linked_m) {
out_cell.interface_input_m->priority_m = --priority_low_m;
}

if (e.relation_count_m)
cells.push_back(&e);
if (out_cell.relation_count_m)
cells.push_back(&out_cell);
else
e.resolved_m = true;
out_cell.resolved_m = true;
}

// Remove the relation from any cells to which it is still attached. That is,
// any cell which is still attached is an "in edge".

vector<name_t> remaining_cells;

for (relation_set_t::iterator tf((*f)->terms_m.begin()), tl((*f)->terms_m.end());
tf != tl; ++tf) {

if (&(*tf) == term)
for (const auto& in_term : relation->terms_m) {
if (&in_term == &(*term))
continue;

remaining_cells.insert(remaining_cells.end(), tf->name_set_m.begin(),
tf->name_set_m.end());
append(remaining_cells, in_term.name_set_m);
}

sort(remaining_cells);
remaining_cells.erase(unique(remaining_cells), remaining_cells.end());

for (vector<name_t>::iterator fc = remaining_cells.begin(), lc = remaining_cells.end();
fc != lc; ++fc) {

index_t::iterator iter = output_index_m.find(*fc);
assert(iter != output_index_m.end());

if (iter->resolved_m)
/// REVISIT (sparent) : Rather than for_each we should have a set of in place transform
/// algorithms. This is transform_if taking a projection and a predicate.
for (const auto& name : remaining_cells) {
auto& cell = output_cell(name);
if (cell.resolved_m)
continue;
--iter->relation_count_m;
--cell.relation_count_m;
}
}
assert(cell.relation_count_m == 0 &&
Expand Down
3 changes: 0 additions & 3 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ function(asl_test)
add_executable (${asl_test_NAME} ${asl_test_SOURCES})

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_options (${asl_test_NAME} PRIVATE "/DNOMINMAX")
target_compile_options (${asl_test_NAME} PRIVATE "/permissive-")
target_compile_options (${asl_test_NAME} PRIVATE "/Zc:__cplusplus")
target_compile_options (${asl_test_NAME} PRIVATE "/W4")
target_compile_options (${asl_test_NAME} PRIVATE "/WX")
else()
Expand Down
3 changes: 0 additions & 3 deletions test/adam_smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ add_executable (adam_smoke_test adam_smoke_test.cpp)
target_link_libraries(adam_smoke_test PRIVATE asl)

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_options (adam_smoke_test PRIVATE "/DNOMINMAX")
target_compile_options (adam_smoke_test PRIVATE "/permissive-")
target_compile_options (adam_smoke_test PRIVATE "/Zc:__cplusplus")
target_compile_options (adam_smoke_test PRIVATE "/W4")
target_compile_options (adam_smoke_test PRIVATE "/WX")
endif()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dabrahams @camio - There must be a better way to manage compiler settings.
I want the library to require the first 3 options, and the cilents (test cases) to add the second 2 options.

Copy link
Contributor

@camio camio Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how I think about it: For portable libraries, the consumer of the library determines the compiler+flags combination for building the library. When the library writer dictates flags, the library gets tied to a snapshot of today's compilers and options making it unportable. The library writer can, however, require certain compiler capabilities.

The requirement underlying the desire for -DNOMINMAX is a WinDef.h that doesn't define min/max macros. I'd enforce this in CMake like this

if(WIN32)
    check_cxx_symbol_exists(min WinDef.h ASL_WINDEF_DEFINES_MIN_MAX)
    if(ASL_WINDEF_DEFINES_MIN_MAX)
        message(FATAL_ERROR "WinDef.h defining `min` and `max` is unsupported by this library. If using MSVC, adding -DNOMINMAX to your compiler flags will address this issue.")
    endif()
endif()

The same goes for the other two options you want to require. The idea is to check for the functionality you require using something like check_cxx_source_compiles and providing helpful messages if they fail.

The last two options that you want for a single file are only useful for developers of this library, but problematic for users and integrators (especially when those flags prevent this library from working with a new compiler version).

If applying these flags to the entire repository isn't feasible, I'd suggest adding a CMake option (defaulting to FALSE) for enabling these flags. Then your code would look something like this:

if (ASL_FORCE_WARNINGS AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
    target_compile_options (adam_smoke_test PRIVATE "/W4")
    target_compile_options (adam_smoke_test PRIVATE "/WX")
endif()

The next question to ask is how are users going to know which flags they need to pass to the various compilers they use. This is what presets are for. With the right preset a user should be able to use cmake --workflow --preset msvc-release to get everything built and tested.

Copy link
Contributor

@dabrahams dabrahams Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If by

I want the library to require the first 3 options and the cilents (test cases) to add the second 2 options.

You actually mean you want the library to be built with the first 3 options and its clients to have the 2nd 2 options imposed on them automatically due to the fact that they link the library, then you want

target_compile_options(the_library PRIVATE  "/Zc:__cplusplus" PUBLIC "/W4" "/WX")

The last two become usage requirements as well as requirements for building the library. To make them pure usage requirements, you'd s/PUBLIC/INTERFACE/.

@camio's statement that:

When the library writer dictates flags, the library gets tied to a snapshot of today's compilers and options making it unportable

Could be interpreted two ways: 1, you should try to use higher-level constructs than the flags for specific compilers whenever possible, or 2, you should never employ usage requirements. I agree with the first but would disagree with the second. I'm not sure what these flags do or whether higher-level constructs exist for them, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated it to use PRIVATE/PUBLIC (more) appropriately.

Expand Down
Loading