Skip to content

[libc++] Make <map> std::map constexpr as part of P3372R3 #134330

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

Open
wants to merge 187 commits into
base: main
Choose a base branch
from

Conversation

vinay-deshmukh
Copy link
Contributor

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

Fixes #128660

Summary:

  1. Apply _LIBCPP_CONSTEXPR_SINCE_CXX26 to map, __tree and node-handle

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

  3. Has a TODO for multimap
    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

  4. TODO: for multimap and others:
    a. libcxx/test/std/containers/container.node/node_handle.pass.cpp

  5. Fix typo in libcxx/include/__memory/pointer_traits.h

  6. 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

  7. Based on this comment, I tried replacing:
    a. __node_traits::construct(__na, std::addressof(__h->__value_), std::forward<_Args>(__args)...);
    b. with
    c. std::__construct_at(std::addressof(__h->__value_), std::forward<_Args>(__args)...);
    d. Fails with:

  # .---command stderr------------
  # | t.tmp.exe: /home/runner/_work/llvm-project/llvm-project/libcxx/test/support/container_test_types.h:399: CopyInsertable<1>::CopyInsertable(const CopyInsertable<Dummy> &) [Dummy = 1]: Assertion `getConstructController()->isInAllocatorConstruct()' failed.
  # `-----------------------------
  # error: command failed with exit status: 250

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.

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 from 181a08d to 6560dd5 Compare April 4, 2025 00:53
@vinay-deshmukh vinay-deshmukh force-pushed the vinay-issue-128660-P3372-constexpr-map branch from 46d0bb9 to 9ea718f Compare April 7, 2025 22:25
__tree<_Tp, _Compare, _Allocator>::__construct_node(_Args&&... __args) {
__node_allocator& __na = __node_alloc();
__node_holder __h(__node_traits::allocate(__na, 1), _Dp(__na));

#if _LIBCPP_STD_VER >= 26
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 we should use something like

std::__construct_at(std::addressof(__h), __na, std::forward<_Args>(__args)...)

in all modes.

And use __node_traits::construct in the constructor of __node.

Copy link
Contributor Author

@vinay-deshmukh vinay-deshmukh Aug 11, 2025

Choose a reason for hiding this comment

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

Thanks!

It works if I don't give the __na argument, as there is no allocator arg here:

_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __construct_at(_Tp* __location, _Args&&... __args) {

Q:

Should this line:

__node_holder __h(__node_traits::allocate(__na, 1), _Dp(__na));

actually be

__node_holder __h(__node_traits::allocate(__na, sizeof(typename __node_holder::element_type)), _Dp(__na));

?

If not, how does it work?

Update:

Allocates n * sizeof(T) bytes of uninitialized storage by calling - https://en.cppreference.com/w/cpp/memory/allocator/allocate.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And use __node_traits::construct in the constructor of __node.

not sure I understand where this is supposed to be as:

https://github.com/llvm/llvm-project/blob/ea14834966ad666856246f24de46397cbfc932eb/libcxx/include/__tree#L754C34-L754C45

is the definition of __node

Copy link
Contributor

Choose a reason for hiding this comment

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

And use __node_traits::construct in the constructor of __node.

not sure I understand where this is supposed to be as:

https://github.com/llvm/llvm-project/blob/ea14834966ad666856246f24de46397cbfc932eb/libcxx/include/__tree#L754C34-L754C45

is the definition of __node

Hmm... __node is actually __tree_node, so we should modify that class. It seems necessary to wrap the __value_ member into a union.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm... __node is actually __tree_node, so we should modify that class. It seems necessary to wrap the _value member into a union.

I think I might be missing something,

Modify the class in what manner? I have added a constructor that was previously missing to __tree_node so I can construct it during constant evaluation.

It seems necessary to wrap the _value member into a union.

What would be the reason to do this?

Something like:

class __tree_node {
    union {
       ... __value_;
       ??? // what would be this part?
    }
};

Copy link
Contributor

Choose a reason for hiding this comment

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

What would be the reason to do this?

We need to use allocator's construct function (if any) to construct the element.

// what would be this part?

I think nothing else should be added to the union. We just need to call __node_traits::construct(__a, std::addressof(__value_), __na, std::forward<_Args>(__args)...) in the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I understand
Let me try this tomorrow.

In all modes

Should this be submitted as a separate patch then? If we're also changing the existing runtime code path

Copy link
Contributor

Choose a reason for hiding this comment

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

In all modes

Should this be submitted as a separate patch then? If we're also changing the existing runtime code path

I think so. But I overlooked C++03 mode before. Perhaps need to do different things (or keep old strategy?) in C++03 mode as some uses of union might be ill-formed.

@vinay-deshmukh vinay-deshmukh changed the title [libc++] Make map constexpr as part of P3372R3 [libc++] Make <map> std::map constexpr as part of P3372R3 Aug 11, 2025
…d.`Revert "PR review comment"

This reverts commit 4fbb472.

Fails with:

```
  # .---command stderr------------
  # | t.tmp.exe: /home/runner/_work/llvm-project/llvm-project/libcxx/test/support/container_test_types.h:399: CopyInsertable<1>::CopyInsertable(const CopyInsertable<Dummy> &) [Dummy = 1]: Assertion `getConstructController()->isInAllocatorConstruct()' failed.
  # `-----------------------------
  # error: command failed with exit status: 250
```
@vinay-deshmukh vinay-deshmukh force-pushed the vinay-issue-128660-P3372-constexpr-map branch from 990228b to efd9f96 Compare August 12, 2025 01:25
@vinay-deshmukh
Copy link
Contributor Author

vinay-deshmukh commented Aug 12, 2025

Not sure what this failure is in gcc-15 tests:

 # .---command stderr------------
  # | /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/containers/associative/map/map.erasure/erase_if.pass.cpp: In function ‘int main(int, char**)’:
  # | /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/containers/associative/map/map.erasure/erase_if.pass.cpp:83:27: error: non-constant condition for static assertion
  # |    83 |   static_assert(test_upper());
  # |       |                 ~~~~~~~~~~^~
  # | In file included from /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/map:599,
  # |                  from /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/containers/associative/map/map.erasure/erase_if.pass.cpp:16:
  # | /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/containers/associative/map/map.erasure/erase_if.pass.cpp:83:27:   in ‘constexpr’ expansion of ‘test_upper()’
  # | /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/containers/associative/map/map.erasure/erase_if.pass.cpp:70:27:   in ‘constexpr’ expansion of ‘test<std::__1::map<int, int> >()’
  # | /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/containers/associative/map/map.erasure/erase_if.pass.cpp:50:11:   in ‘constexpr’ expansion of ‘test0<std::__1::map<int, int>, test<std::__1::map<int, int> >()::<lambda(auto:32)> >(Init{((const int*)(& const int [1]{1})), 1}, (is1, test<std::__1::map<int, int> >()::<lambda(auto:32)>()), Init{0, 0}, 1)’
  # | /home/runner/_work/llvm-project/llvm-project/libcxx/test/std/containers/associative/map/map.erasure/erase_if.pass.cpp:35:3:   in ‘constexpr’ expansion of ‘std::__1::erase_if<int, int, less<int>, allocator<pair<const int, int> >, test<std::__1::map<int, int> >()::<lambda(auto:32)> >(s, (p, test<std::__1::map<int, int> >()::<lambda(auto:32)>()))’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/map:1605:42:   in ‘constexpr’ expansion of ‘std::__1::__libcpp_erase_if_container<map<int, int>, test<std::__1::map<int, int> >()::<lambda(auto:32)> >((* & __c), __pred)’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/__iterator/erase_if_container.h:32:25:   in ‘constexpr’ expansion of ‘(& __c)->std::__1::map<int, int>::erase(__iter)’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/map:1256:106:   in ‘constexpr’ expansion of ‘((std::__1::map<int, int>*)this)->std::__1::map<int, int>::__tree_.std::__1::__tree<std::__1::__value_type<int, int>, std::__1::__map_value_compare<int, std::__1::pair<const int, int>, std::__1::less<int>, true>, std::__1::allocator<std::__1::pair<const int, int> > >::erase(std::__1::__tree_const_iterator<std::__1::__value_type<int, int>, std::__1::__tree_node<std::__1::__value_type<int, int>, void*>*, long int>(__p.std::__1::__map_iterator<std::__1::__tree_iterator<std::__1::__value_type<int, int>, std::__1::__tree_node<std::__1::__value_type<int, int>, void*>*, long int> >::__i_))’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/__tree:2132:49:   in ‘constexpr’ expansion of ‘((std::__1::__tree<std::__1::__value_type<int, int>, std::__1::__map_value_compare<int, std::__1::pair<const int, int>, std::__1::less<int>, true>, std::__1::allocator<std::__1::pair<const int, int> > >*)this)->std::__1::__tree<std::__1::__value_type<int, int>, std::__1::__map_value_compare<int, std::__1::pair<const int, int>, std::__1::less<int>, true>, std::__1::allocator<std::__1::pair<const int, int> > >::__remove_node_pointer(__np)’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/__tree:1997:3:   in ‘constexpr’ expansion of ‘__r.std::__1::__tree_iterator<std::__1::__value_type<int, int>, std::__1::__tree_node<std::__1::__value_type<int, int>, void*>*, long int>::operator++()’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/__tree:644:55:   in ‘constexpr’ expansion of ‘std::__1::__tree_next_iter<__tree_end_node<__tree_node_base<void*>*>*, __tree_node_base<void*>*>(std::__1::__static_fancy_pointer_cast<__tree_node_base<void*>*, __tree_end_node<__tree_node_base<void*>*>*>(((std::__1::__tree_iterator<std::__1::__value_type<int, int>, std::__1::__tree_node<std::__1::__value_type<int, int>, void*>*, long int>*)this)->std::__1::__tree_iterator<std::__1::__value_type<int, int>, std::__1::__tree_node<std::__1::__value_type<int, int>, void*>*, long int>::__ptr_))’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/__tree:178:36:   in ‘constexpr’ expansion of ‘std::__1::__tree_is_left_child<__tree_node_base<void*>*>(__x)’
  # | /home/runner/_work/llvm-project/llvm-project/build/generic-gcc/libcxx/test-suite-install/include/c++/v1/__tree:90:33: error: ‘‘result_decl’ not supported by dump_expr<expression error>’ is not a constant expression
  # |    90 |   return __x == __x->__parent_->__left_;
  # |       |                 ~~~~~~~~~~~~~~~~^~~~~~~
  # | At global scope:
  # | cc1plus: note: unrecognized command-line option ‘-Wno-nullability-completeness’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-pass-failed’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-local-type-template-args’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-unused-local-typedef’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-user-defined-literals’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-reserved-module-identifier’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-atomic-alignment’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-aligned-allocation-unavailable’ may have been intended to silence earlier diagnostics
  # | cc1plus: note: unrecognized command-line option ‘-Wno-unused-command-line-argument’ may have been intended to silence earlier diagnostics
  # `-----------------------------
  # error: command failed with exit status: 1
  

AIX 32 fails with the following and basically the whole test suite(not sure if it has the up to date compiler)

AIX is using Clang 19, so about a year old and that may be the cause of this failure:


# .---command stderr------------
--
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/libcxx/test/std/containers/associative/map/compare.pass.cpp:59:17: error: static assertion expression is not an integral constant expression
  | # \|    59 \|   static_assert(test());
  | # \|       \|                 ^~~~~~
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/build/aix/libcxx/test-suite-install/include/c++/v1/__memory/construct_at.h:38:49: note: non-literal type 'std::__tree_node<std::__value_type<Key, int>, void *>' cannot be used in a constant expression
  | # \|    38 \|   return ::new (static_cast<void*>(__location)) _Tp(std::forward<_Args>(__args)...);
  | # \|       \|                                                 ^
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/build/aix/libcxx/test-suite-install/include/c++/v1/__memory/construct_at.h:46:10: note: in call to 'construct_at<std::__tree_node<std::__value_type<Key, int>, void *>, const std::piecewise_construct_t &, std::tuple<Key &&>, std::tuple<>, std::__tree_node<std::__value_type<Key, int>, void *> *>(&{*new std::__tree_node<std::__value_type<Key, int>, void *>[1]#0}[0], piecewise_construct, std::forward_as_tuple(std::move(__k)), std::forward_as_tuple())'
  | # \|    46 \|   return std::construct_at(__location, std::forward<_Args>(__args)...);
  | # \|       \|          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/build/aix/libcxx/test-suite-install/include/c++/v1/__tree:1893:5: note: in call to '__construct_at<std::__tree_node<std::__value_type<Key, int>, void *>, const std::piecewise_construct_t &, std::tuple<Key &&>, std::tuple<>, std::__tree_node<std::__value_type<Key, int>, void *> *>(&{*new std::__tree_node<std::__value_type<Key, int>, void *>[1]#0}[0], piecewise_construct, std::forward_as_tuple(std::move(__k)), std::forward_as_tuple())'
  | # \|  1893 \|     std::__construct_at(std::addressof(*__h), std::forward<_Args>(__args)...);
  | # \|       \|     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/build/aix/libcxx/test-suite-install/include/c++/v1/__tree:1856:25: note: in call to 'this->__construct_node<const std::piecewise_construct_t &, std::tuple<Key &&>, std::tuple<>>(piecewise_construct, std::forward_as_tuple(std::move(__k)), std::forward_as_tuple())'
  | # \|  1856 \|     __node_holder __h = __construct_node(std::forward<_Args>(__args)...);
  | # \|       \|                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/build/aix/libcxx/test-suite-install/include/c++/v1/map:1492:10: note: in call to 'this->__tree_.__emplace_unique_key_args<Key, const std::piecewise_construct_t &, std::tuple<Key &&>, std::tuple<>>(Key(0), piecewise_construct, std::forward_as_tuple(std::move(__k)), std::forward_as_tuple())'
  | # \|  1492 \|   return __tree_
  | # \|       \|          ^~~~~~~
  | # \|  1493 \|       .__emplace_unique_key_args(
  | # \|       \|       ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  | # \|  1494 \|           __k, std::piecewise_construct, std::forward_as_tuple(std::move(__k)), std::forward_as_tuple())
  | # \|       \|           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/libcxx/test/std/containers/associative/map/compare.pass.cpp:37:5: note: in call to 'm_contains.operator[](Key(0))'
  | # \|    37 \|     m_contains[Key(0)] = 42;
  | # \|       \|     ^~~~~~~~~~~~~~~~~~
  | # \| /scratch/powerllvm/cpap8006/llvm-project/libcxx-ci/libcxx/test/std/containers/associative/map/compare.pass.cpp:59:17: note: in call to 'test()'
  | # \|    59 \|   static_assert(test());
  | # \|       \|                 ^~~~~~
  | # \| 1 error generated.
  | # `-----------------------------
  | # error: command failed with exit status: 1
  |  
  | --


@vinay-deshmukh vinay-deshmukh marked this pull request as ready for review August 12, 2025 02:39
@vinay-deshmukh vinay-deshmukh requested a review from a team as a code owner August 12, 2025 02:39
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.

[libc++] P3372R3: constexpr map
2 participants