Skip to content

Conversation

@vinay-deshmukh
Copy link
Contributor

@vinay-deshmukh vinay-deshmukh commented Apr 4, 2025

Fixes #128660

Depends on:

  1. [NFC][libc++] Fix typo in libcxx/include/__memory/pointer_traits.h #157304
  2. [libc++] Remove UB from std::__tree_node construction #153908

Summary:

  1. Apply _LIBCPP_CONSTEXPR_SINCE_CXX26 to map, __tree, node-handle, __libcpp_erase_if, __lazy_synth_three_way_comparator, __lazy_compare_result, libcxx/include/__utility/try_key_extraction.h

  2. Skip test erase_if.pass.cpp for GCC during constant evaluation. AFAICT this appears to be a g++ bug, as the test passes with clang

  3. Disable test for node-handle::key() during constant evaluation (CWG2514). It is annotated with a // FIXME

  4. map.modifiers/try.emplace.pass.cpp : Start using the previously unused mv3. (Should this be a separate patch?)

  5. Has a TODO for multimap and ohters
    a. libcxx/test/std/containers/associative/map/map.ops/contains.pass.cpp
    b. libcxx/test/std/containers/associative/map/map.ops/contains_transparent.pass.cpp
    a. libcxx/test/std/containers/container.node/node_handle.pass.cpp

  6. Fix typo in libcxx/include/__memory/pointer_traits.h -> [NFC][libc++] Fix typo in libcxx/include/__memory/pointer_traits.h #157304

  7. pair<const MoveOnly, ...>
    a. move_assign.pass.cpp
    b. move_alloc.pass.cpp
    c. Fails to compile if static_assert(test()); is called in the test file
    d. Has a // FIXME with commented code

  8. Change __tree_node to construct it's __node_value_type during construction to avoid the: note: member call on object outside its lifetime is not allowed in a constant expression issue. This became -> [libc++] Remove UB from std::__tree_node construction #153908

@github-actions
Copy link

github-actions bot commented Apr 4, 2025

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Discourse for more information.

@github-actions
Copy link

github-actions bot commented Apr 4, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@vinay-deshmukh vinay-deshmukh force-pushed the vinay-issue-128660-P3372-constexpr-map branch 2 times, most recently from 46d0bb9 to 9ea718f Compare April 7, 2025 22:25
@vinay-deshmukh
Copy link
Contributor Author

At this point, I have fixed all the trivially fixable issues as far as I can tell, but naturally trying to fix the lifetime issue I saw when trying to fix it in:

cec33fb

(need explicit constructors for std::__value_type and __tree_node_base to start the lifetime of a node at

https://github.com/llvm/llvm-project/blame/900d44976c89477607946fad4493e4b9ac09346f/libcxx/include/__tree#L1956)

And that has resulted in a im-perfect forwarding (I think)

I did find this commit that mentions that the code is UB (slightly less than previously), so maybe constexpr flagging that is expected

f52318b | https://reviews.llvm.org/D47607

@frederick-vs-ja

@frederick-vs-ja
Copy link
Contributor

I did find this commit that mentions that the code is UB (slightly less than previously), so maybe constexpr flagging that is expected

f52318b | https://reviews.llvm.org/D47607

This is clearly UB in the core language. Per current rules we can't assign the first data member of a pair<const int, int>, even when the complete object is non-const.

IIUC we need to perform different operations (e.g. reconstructing the node) in constant evaluation.

@vinay-deshmukh
Copy link
Contributor Author

Update:

Need to wait for #134819 to be merged before this PR can proceed.

That PR should get rid of some more UB, and later I should be able to:

There's still some UB left, but that' only in the __assign_value and __insert_from_orphaned_node functions, where you should be able to just destroy/construct instead of assigning to remove any UB during constant evaluation.

From Discord by philnik777

@vinay-deshmukh vinay-deshmukh force-pushed the vinay-issue-128660-P3372-constexpr-map branch from 7906780 to 0cce96e Compare June 9, 2025 01:07
// Clang doesn't optimize on this currently though.
const_cast<__key_type&>(__lhs.first) = const_cast<__copy_cvref_t<_From, __key_type>&&>(__rhs.first);
__lhs.second = std::forward<_From>(__rhs).second;
if (std::is_constant_evaluated()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

<__type_traits/is_constant_evaluated.h> should be included for this. If you decide not to guard this with _LIBCXX_STD_VER >= 26, __libcpp_is_constant_evaluated() should be used instead.

Copy link
Contributor Author

@vinay-deshmukh vinay-deshmukh Jun 15, 2025

Choose a reason for hiding this comment

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

I think since the method itself is marked as _LIBCPP_CONSTEXPR_SINCE_CXX26 the extra guard may not be needed and it should be fine to use the std::is_constant_evaluated() variant as this would be the first time this method is valid to be used in constant evaluation.
https://godbolt.org/z/b91EK95hj

edit:
never mind, just realized, it won't compile below cpp20 probably if i use the std:: version

// tmp.second = std::forward<_From>(__rhs).second;
// __lhs = pair<const int, double>
// TODO: use a better name
using foo_type = __get_node_value_type_t<value_type>;
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
using foo_type = __get_node_value_type_t<value_type>;
using __node_value_type = __get_node_value_type_t<value_type>;

_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
__insert_unique_from_orphaned_node(const_iterator __p, __get_node_value_type_t<_Tp>&& __value) {
using __key_type = typename _NodeTypes::key_type;
// fails here for move_alloc.pass.cpp
Copy link
Contributor

Choose a reason for hiding this comment

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

There seem to be obstacles that can't be worked around. I guess we should skip some test cases for constant evaluation at this moment, and submit an LWG issue (even though there's already CWG2514).

Comment on lines 1450 to 1465
// we use copy, and not "move" as the constraint
// because we can NOT move from `const key_type`, which is how `value_type` is defined
// atleast for map
// typedef pair<const key_type, mapped_type> value_type;
// so we must copy it

// const_cast is not allowed at constexpr time.
// we get around this by deleting __lhs and creating a new node in-place
// to avoid const_cast __lhs.first

// We create a sfinae wrapper method here, because if the body of the true_type overload for
// __assign_value__sfinae() gets template instantiated within __assign_value, the code will fail to compile where
// the value is not copy_constructible for runtime execution as well; unless we use `if constexpr`. Given the
// copy-constructible code path will be a performance regression, we want to restrict it to only execute during
// constant evaluation
//, we need to delay the template instantiation
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit formatted oddly, could you please update it and add punctuation, e.g (not complete).

Suggested change
// we use copy, and not "move" as the constraint
// because we can NOT move from `const key_type`, which is how `value_type` is defined
// atleast for map
// typedef pair<const key_type, mapped_type> value_type;
// so we must copy it
// const_cast is not allowed at constexpr time.
// we get around this by deleting __lhs and creating a new node in-place
// to avoid const_cast __lhs.first
// We create a sfinae wrapper method here, because if the body of the true_type overload for
// __assign_value__sfinae() gets template instantiated within __assign_value, the code will fail to compile where
// the value is not copy_constructible for runtime execution as well; unless we use `if constexpr`. Given the
// copy-constructible code path will be a performance regression, we want to restrict it to only execute during
// constant evaluation
//, we need to delay the template instantiation
// We use copy, and not "move" as the constraint because we can NOT move from `const key_type`, which is how `value_type` is defined
// atleast for map
// typedef pair<const key_type, mapped_type> value_type;
// so we must copy it
// const_cast is not allowed at constexpr time.
// we get around this by deleting __lhs and creating a new node in-place
// to avoid const_cast __lhs.first
// We create a sfinae wrapper method here, because if the body of the true_type overload for
// __assign_value__sfinae() gets template instantiated within __assign_value, the code will fail to compile where
// the value is not copy_constructible for runtime execution as well; unless we use `if constexpr`. Given the
// copy-constructible code path will be a performance regression, we want to restrict it to only execute during
// constant evaluation, we need to delay the template instantiation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a few . and capitalized first letters of sentences. Rewrote some parts.

69bb9d3

let me know if this is better!

Comment on lines 139 to 142
constexpr iterator emplace_hint(const_iterator position, Args&&... args); // constexpr since C++26
constexpr pair<iterator, bool> insert(const value_type& v); // constexpr since C++26
constexpr pair<iterator, bool> insert( value_type&& v); // C++17, constexpr
since C++26
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
constexpr iterator emplace_hint(const_iterator position, Args&&... args); // constexpr since C++26
constexpr pair<iterator, bool> insert(const value_type& v); // constexpr since C++26
constexpr pair<iterator, bool> insert( value_type&& v); // C++17, constexpr
since C++26
constexpr iterator emplace_hint(const_iterator position, Args&&... args); // constexpr since C++26
constexpr pair<iterator, bool> insert(const value_type& v); // constexpr since C++26
constexpr pair<iterator, bool> insert( value_type&& v); // C++17, constexpr since C++26

Can you align these comments a bit?

Copy link
Contributor Author

@vinay-deshmukh vinay-deshmukh Oct 19, 2025

Choose a reason for hiding this comment

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

Done in
66b4d6f

Aligned it to each "stanza" / section, let me know if this looks good

@vinay-deshmukh vinay-deshmukh force-pushed the vinay-issue-128660-P3372-constexpr-map branch from 8658eee to 66b4d6f Compare October 19, 2025 10:27
@vinay-deshmukh
Copy link
Contributor Author

The latest failure:

  diff -u /home/gha/actions-runner/_work/llvm-project/llvm-project/build/generic-cxx26/libcxx/test/libcxx/module_std_compat.gen.py/Output/module_std_compat.sh.cpp.dir/t.tmp.cuchar.module /home/gha/actions-runner/_work/llvm-project/llvm-project/build/generic-cxx26/libcxx/test/libcxx/module_std_compat.gen.py/Output/module_std_compat.sh.cpp.dir/t.tmp.cuchar.include
  # executed command: diff -u /home/gha/actions-runner/_work/llvm-project/llvm-project/build/generic-cxx26/libcxx/test/libcxx/module_std_compat.gen.py/Output/module_std_compat.sh.cpp.dir/t.tmp.cuchar.module /home/gha/actions-runner/_work/llvm-project/llvm-project/build/generic-cxx26/libcxx/test/libcxx/module_std_compat.gen.py/Output/module_std_compat.sh.cpp.dir/t.tmp.cuchar.include
  # .---command stdout------------
  # | --- /home/gha/actions-runner/_work/llvm-project/llvm-project/build/generic-cxx26/libcxx/test/libcxx/module_std_compat.gen.py/Output/module_std_compat.sh.cpp.dir/t.tmp.cuchar.module
  # | +++ /home/gha/actions-runner/_work/llvm-project/llvm-project/build/generic-cxx26/libcxx/test/libcxx/module_std_compat.gen.py/Output/module_std_compat.sh.cpp.dir/t.tmp.cuchar.include
  # | @@ -1,9 +1,7 @@
  # |  using ::c16rtomb;
  # |  using ::c32rtomb;
  # | -using ::c8rtomb;
  # |  using ::mbrtoc16;
  # |  using ::mbrtoc32;
  # | -using ::mbrtoc8;
  # |  using ::std::c16rtomb;
  # |  using ::std::c32rtomb;
  # |  using ::std::c8rtomb;
  # `-----------------------------
  # error: command failed with exit status: 1

seems to be related to this: #152724

not sure why that is failing however

@vinay-deshmukh
Copy link
Contributor Author

@frederick-vs-ja @philnik777 @H-G-Hristov @Zingam

Can you take a look at this pull request when you have a chance? Thank you!

@vinay-deshmukh
Copy link
Contributor Author

@vinay-deshmukh vinay-deshmukh force-pushed the vinay-issue-128660-P3372-constexpr-map branch from 1118540 to 4af614d Compare November 10, 2025 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++26 libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[libc++] P3372R3: constexpr map

7 participants