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

MSVC C++20 Support #126

merged 20 commits into from
Nov 13, 2024

Conversation

sean-parent
Copy link
Member

@sean-parent sean-parent commented Nov 7, 2024

Added Support for MSVC C++20.
Fixed warnings at \W4
Improved cmake presets support for VSCode.
New libraries:

  • macro_utilities.hpp
    • ADOBE_STRINGIZE(x)
    • ADOBE_LINE_STRING()
    • ADOBE_MESSAGE(message)
  • exception.hpp
    • terminate(message)
  • append.hpp
    • append(container, range)
  • unique.hpp
    • sort_unique(container)

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.

adobe/manip.hpp Outdated
However, if it is not included here, the build fails for C++17 under MSVC only. I have not been
able to determine why this is the case.
*/
#include <adobe/serializable.hpp>
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 - I was able to significantly reduce the problem to the pointer where I determined that adobe/serializable.hpp had to be included before manip.hpp, but there does not appear to be any dependency on serializable.hpp - circular or not. In fact, in my reduced case, not include manip.hpp at all also worked. If it was more than one configuration of one compiler, I'd reduce it more.

Wihtout this line the following fails to compiler:

#include <adobe/array.hpp>
#include <adobe/manip.hpp>
#include <adobe/serializable.hpp>

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

namespace adobe {

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

void test() {
    serializable_t value{array_t{}};
}

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

} // namespace adobe

Copy link
Contributor

Choose a reason for hiding this comment

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

Without what line?

I don't think giving up on the reduction is appropriate. This configuration of this compiler applies all Adobe products and many others. IMO you should get to the point where you can submit a bug report. But that's just me.

Copy link
Member Author

Choose a reason for hiding this comment

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

Found it - a namespace lookup issue. Fixed property in array.hpp.

Copy link
Contributor

@dabrahams dabrahams left a comment

Choose a reason for hiding this comment

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

Some suggestions inline.

.vscode/settings.json Outdated Show resolved Hide resolved
@@ -7,8 +7,7 @@ project(adobe_source_libraries CXX)

set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++17")
set(CMAKE_CXX_STANDARD 20)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to assume that dropping the Xcode setting is intentional and correct.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. It isn't needed.

"value": "x64",
"strategy": "external"
},
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
Copy link
Contributor

Choose a reason for hiding this comment

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

So this toolchain requires vcpkg? Is that dependency documented in the README? If not you better do that.
Is how to use the presets documented?

Copy link
Member Author

Choose a reason for hiding this comment

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

The system is currently using vcpkg to pull in boost. It's undocumented, and I'd like to get rid of it in favor of fetch content, but not in this PR.


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

/// invoke `std::terminate` with the given message.
Copy link
Contributor

Choose a reason for hiding this comment

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

? You can't invoke std::terminate with a message, so you need to fix this.

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 the comment to be more clear.

@@ -227,7 +227,7 @@ class byte_toroid_t : boost::noncopyable {

void purify(toroid_node_t* p) {
toroid_header_t* c(column_of(p));
int x(p->color_m);
auto x(p->color_m);
Copy link
Contributor

Choose a reason for hiding this comment

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

Surely not needed for C++20 support? Why is it even an improvement?

Aside: we really don't use copy-initialization anymore? This syntax makes it look like a function declaration.

Copy link
Member Author

Choose a reason for hiding this comment

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

This silences a warning because x is char type and later uses will narrow back to a char type.

stuff_bit_offset += num_bits;
n_stuffed_bits += num_bits;
ADOBE_ASSERT(num_bits < 8);
stuff_bit_offset += static_cast<std::uint16_t>(num_bits);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a comment explaining how you know it won't overflow is warranted.

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'm not an expert in this code - it took me a bit to figure out that num_bits < 8 here. I'm not certain, but it looks like stuff_bit_offset is always used in modulo value_bitsize_k and can warp (unsigned). n_stuffed_bits is 64 bits, so it would take a lot to overflow incrementing by 8. And I think this is going to be bounded by sizeof message_block_type * 8 - so you'll run out of VM space first (if not addressable space).

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, if you can't prove it, there's always boost::numeric_cast to maintain safety.

Copy link
Member Author

Choose a reason for hiding this comment

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

That might be very bad if it is expected to wrap.

source/adam.cpp Outdated
@@ -1114,36 +1114,34 @@ void sheet_t::implementation_t::flow(cell_bits_t& priority_accessed) {
index_t::iterator iter = output_index_m.find(term->name_set_m[n]);
assert(iter != output_index_m.end());

cell_t& cell = *iter;
cell_t& e = *iter;
Copy link
Contributor

Choose a reason for hiding this comment

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

OMG this is a hecka long function. Nice getting rid of the type information in the name, but can we do better than e? What role is this variable playing on the inside of the (raw!) loop? That is, if you were to break the body of the loop into a function call taking cell_t&, what would you call the parameter?

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 did a fair amount of refactoring and cleanup of that function and used better names. Still long - opened Issue#127 to revisit.

source/eve.cpp Outdated
for (guide_set_t::iterator first(boost::next(forward_guide_set.begin())),
last(forward_guide_set.end());
first != last; ++first) {
for (guide_set_t::iterator f(boost::next(forward_guide_set.begin())),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this an improvement?

Copy link
Member Author

Choose a reason for hiding this comment

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

Was a quick way to squelch a warning - but I resolved the revisit comment and moved the code to use std::generate()

for (array_t::const_iterator iter(spacing_array.begin());
iter != spacing_array.end(); ++iter) {
*dest_iter = iter->cast<int>();
for (array_t::const_iterator spacing_iter(spacing_array.begin());
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a single character name would actually be clearer in this case. Also, though, no raw loops would alleviate the need to name these trivial things everywhere.

Copy link
Member Author

Choose a reason for hiding this comment

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

Replaced this one with transform()

@@ -151,7 +151,7 @@ void flat_format::handle_atom(stream_type& os, bool is_push) {
} else if (value.type_info() == typeid(dictionary_t)) {
os << value.cast<dictionary_t>();
} else if (value.type_info() == typeid(array_t)) {
os << value.cast<array_t>();
os << top.value().cast<::adobe::array_t>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably you want to revert this change, but if not, comment to explain why this line needs to be so different from the rest.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Reducing loops in sheet flow
Added append algorithm
Comment on lines 17 to 21
/// 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.

source/adam.cpp Outdated
Comment on lines 349 to 350
/// 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.

source/adam.cpp Outdated
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" -

@sean-parent sean-parent merged commit ea5e048 into main Nov 13, 2024
9 checks passed
@sean-parent sean-parent deleted the msvc-cpp-20-support branch November 13, 2024 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants