Skip to content

[libc++] constexpr deque #129368

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

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

changkhothuychung
Copy link
Contributor

@changkhothuychung changkhothuychung commented Mar 1, 2025

Fixes #128656

@changkhothuychung changkhothuychung requested a review from a team as a code owner March 1, 2025 07:08
@changkhothuychung changkhothuychung marked this pull request as draft March 1, 2025 07:08
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Mar 1, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 1, 2025

@llvm/pr-subscribers-libcxx

Author: Nhat Nguyen (changkhothuychung)

Changes

Full diff: https://github.com/llvm/llvm-project/pull/129368.diff

1 Files Affected:

  • (modified) libcxx/include/deque (+6-5)
diff --git a/libcxx/include/deque b/libcxx/include/deque
index 95200b4801d7f..c92b89aa9f1c0 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -2543,7 +2543,8 @@ inline void deque<_Tp, _Allocator>::clear() _NOEXCEPT {
 }
 
 template <class _Tp, class _Allocator>
-inline _LIBCPP_HIDE_FROM_ABI bool operator==(const deque<_Tp, _Allocator>& __x, const deque<_Tp, _Allocator>& __y) {
+inline _LIBCPP_HIDE_FROM_ABI constexpr bool
+operator==(const deque<_Tp, _Allocator>& __x, const deque<_Tp, _Allocator>& __y) {
   const typename deque<_Tp, _Allocator>::size_type __sz = __x.size();
   return __sz == __y.size() && std::equal(__x.begin(), __x.end(), __y.begin());
 }
@@ -2578,7 +2579,7 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator<=(const deque<_Tp, _Allocator>& __x,
 #  else // _LIBCPP_STD_VER <= 17
 
 template <class _Tp, class _Allocator>
-_LIBCPP_HIDE_FROM_ABI __synth_three_way_result<_Tp>
+_LIBCPP_HIDE_FROM_ABI __synth_three_way_result<_Tp> constexpr
 operator<=>(const deque<_Tp, _Allocator>& __x, const deque<_Tp, _Allocator>& __y) {
   return std::lexicographical_compare_three_way(__x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
 }
@@ -2586,14 +2587,14 @@ operator<=>(const deque<_Tp, _Allocator>& __x, const deque<_Tp, _Allocator>& __y
 #  endif // _LIBCPP_STD_VER <= 17
 
 template <class _Tp, class _Allocator>
-inline _LIBCPP_HIDE_FROM_ABI void swap(deque<_Tp, _Allocator>& __x, deque<_Tp, _Allocator>& __y)
+inline _LIBCPP_HIDE_FROM_ABI constexpr void swap(deque<_Tp, _Allocator>& __x, deque<_Tp, _Allocator>& __y)
     _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) {
   __x.swap(__y);
 }
 
 #  if _LIBCPP_STD_VER >= 20
 template <class _Tp, class _Allocator, class _Up>
-inline _LIBCPP_HIDE_FROM_ABI typename deque<_Tp, _Allocator>::size_type
+inline _LIBCPP_HIDE_FROM_ABI constexpr typename deque<_Tp, _Allocator>::size_type
 erase(deque<_Tp, _Allocator>& __c, const _Up& __v) {
   auto __old_size = __c.size();
   __c.erase(std::remove(__c.begin(), __c.end(), __v), __c.end());
@@ -2601,7 +2602,7 @@ erase(deque<_Tp, _Allocator>& __c, const _Up& __v) {
 }
 
 template <class _Tp, class _Allocator, class _Predicate>
-inline _LIBCPP_HIDE_FROM_ABI typename deque<_Tp, _Allocator>::size_type
+inline _LIBCPP_HIDE_FROM_ABI constexpr typename deque<_Tp, _Allocator>::size_type
 erase_if(deque<_Tp, _Allocator>& __c, _Predicate __pred) {
   auto __old_size = __c.size();
   __c.erase(std::remove_if(__c.begin(), __c.end(), __pred), __c.end());

Copy link
Contributor

@frederick-vs-ja frederick-vs-ja left a comment

Choose a reason for hiding this comment

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

Thanks! This PR should have Fixes #128656., and There are some changes to be done.

  1. Add _LIBCPP_CONSTEXPR_SINCE_CXX26 to functions in <deque>.
  2. Modify test files (.pass.cpp) in the libcxx/test/std/containers/sequences/deque subdirectory. The following pattern can be used.
TEST_CONSTEXPR_CXX26 bool test() {
  /* existing contents */
  return true;
}

int main(int, char**) {
  test();
#if TEST_STD_VER >= 26
  static_assert(test());
#endif
}
  1. Add test macro entry for __cpp_lib_constexpr_deque (of value 202502, also in <deque>) in generate_feature_test_macro_components.py and then run the script.

@changkhothuychung
Copy link
Contributor Author

Hmm it seems definitions of constexpr void functions are being rejected as void is not a literal type. But I see cv void is listed as a literal type here - https://eel.is/c++draft/basic.types#general-10.1

Am I missing something?

@frederick-vs-ja
Copy link
Contributor

Hmm it seems definitions of constexpr void functions are being rejected as void is not a literal type. But I see cv void is listed as a literal type here - https://eel.is/c++draft/basic.types#general-10.1

Am I missing something?

Ah, cv void types are literal types only since C++14, but this shouldn't matter because we're adding constexpr since C++26.

Copy link

github-actions bot commented Mar 2, 2025

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

@changkhothuychung changkhothuychung marked this pull request as ready for review April 9, 2025 03:20
@philnik777
Copy link
Contributor

@changkhothuychung Can you please make sure the tests (at least relevant to your patch) pass locally before pushing changes?

@changkhothuychung changkhothuychung marked this pull request as draft April 10, 2025 04:30
Copy link
Contributor Author

@changkhothuychung changkhothuychung left a comment

Choose a reason for hiding this comment

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

having some questions

@@ -619,19 +620,20 @@ public:
__alloc_traits::deallocate(__alloc(), *__i, __block_size);
}

_LIBCPP_HIDE_FROM_ABI explicit deque(const allocator_type& __a)
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 explicit deque(const allocator_type& __a)
: __map_(__pointer_allocator(__a)), __start_(0), __size_(0), __alloc_(__a) {
__annotate_new(0);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@frederick-vs-ja when trying to call the test in a constexpr case, via static_assert, I got error that __annotate_new (0) cannot be used in a constant expression, this function is called within a constexpr constructor. How should I handle this case? Can __annotate_new be made constexpr ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. I think we can mark most underlying ASAN-related functions (which are not previously constexpr) _LIBCPP_CONSTEXPR_SINCE_CXX26 and add if (__libcpp_is_constant_evaluated()) return; to suitable places.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@frederick-vs-ja can you give me an example of where I can put if (__libcpp_is_constant_evaluated()) return; ? I am not sure how I can apply that function.

Copy link
Contributor

Choose a reason for hiding this comment

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

The error messages look a bit chaotic. Could you try again after finish adding _LIBCPP_CONSTEXPR_SINCE_CXX26 to <deque>?

@changkhothuychung
Copy link
Contributor Author

Im running into another case, an example of such case is located in the file libcxx/test/std/containers/sequences/deque/deque.cons/move_assign.pass.cpp

When doing assertion assert(c2 == c3) for min_allocator, I ran into this error below. It seems the function is marked as constexpr but has not been tested for constant expression before.

In this case, we are trying to initialize min_pointer<const min_pointer<const MoveOnly, ID>, ID> from a min_pointer<const min_pointer<MoveOnly, ID>, ID>, the constructor min_pointer(min_pointer<const void, ID> p) is invoked, but the static cast fails.

The constructor min_pointer(min_pointer<T, ID> p) fails because min_pointer<T, ID> in this case is min_pointer<min_pointer<const MoveOnly, ID>, ID>, and there is no conversion to convert min_pointer<const min_pointer<MoveOnly, ID>, ID> to it.

@frederick-vs-ja Do you know how to fix this?

 | /Users/nguyennhat/Desktop/llvm-2/libcxx/test/std/containers/sequences/deque/deque.cons/move_assign.pass.cpp:107:17: error: static assertion expression is not an integral constant expression
# |   107 |   static_assert(test());
# |       |                 ^~~~~~
# | /Users/nguyennhat/Desktop/llvm-2/libcxx/test/support/min_allocator.h:322:85: note: cast from 'const void *' is not allowed in a constant expression because the pointed object type 'min_pointer<MoveOnly>' is not similar to the target type 'const min_pointer<const MoveOnly>'
# |   322 |     TEST_CONSTEXPR_CXX14 explicit min_pointer(min_pointer<const void, ID> p) : ptr_(static_cast<const T*>(p.ptr_)) {}
# |       |                                                                                     ^
# | /Users/nguyennhat/Desktop/llvm-2/build/runtimes/runtimes-bins/libcxx/test-suite-install/include/c++/v1/deque:729:32: note: in call to 'min_pointer({&{*new min_pointer<MoveOnly>[1]#16}[0]})'
# |   729 |     __map_const_pointer __mp = static_cast<__map_const_pointer>(__map_.begin() + __start_ / __block_size);
# |       |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | /Users/nguyennhat/Desktop/llvm-2/build/runtimes/runtimes-bins/libcxx/test-suite-install/include/c++/v1/deque:2570:43: note: in call to '__x.begin()'
# |  2570 |   return __sz == __y.size() && std::equal(__x.begin(), __x.end(), __y.begin());
# |       |                                           ^~~~~~~~~~~
# | /Users/nguyennhat/Desktop/llvm-2/libcxx/test/std/containers/sequences/deque/deque.cons/move_assign.pass.cpp:94:12: note: in call to 'operator==<MoveOnly, min_allocator<MoveOnly>>(c2, c3)'
# |    94 |     assert(c2 == c3);

@frederick-vs-ja
Copy link
Contributor

The constructor min_pointer(min_pointer<T, ID> p) fails because min_pointer<T, ID> in this case is min_pointer<min_pointer<const MoveOnly, ID>, ID>, and there is no conversion to convert min_pointer<const min_pointer<MoveOnly, ID>, ID> to it.

It seems that libc++'s deque::const_iterator is misdesigned and contains core language UB. I think the __map_const_pointer member typedef should be typename allocator_traits<__pointer_allocator>::const_pointer.

@frederick-vs-ja
Copy link
Contributor

Im running into another case, an example of such case is located in the file libcxx/test/std/containers/sequences/deque/deque.cons/move_assign.pass.cpp

When doing assertion assert(c2 == c3) for min_allocator, I ran into this error below. It seems the function is marked as constexpr but has not been tested for constant expression before.

In this case, we are trying to initialize min_pointer<const min_pointer<const MoveOnly, ID>, ID> from a min_pointer<const min_pointer<MoveOnly, ID>, ID>, the constructor min_pointer(min_pointer<const void, ID> p) is invoked, but the static cast fails.

The constructor min_pointer(min_pointer<T, ID> p) fails because min_pointer<T, ID> in this case is min_pointer<min_pointer<const MoveOnly, ID>, ID>, and there is no conversion to convert min_pointer<const min_pointer<MoveOnly, ID>, ID> to it.

@frederick-vs-ja Do you know how to fix this?

 | /Users/nguyennhat/Desktop/llvm-2/libcxx/test/std/containers/sequences/deque/deque.cons/move_assign.pass.cpp:107:17: error: static assertion expression is not an integral constant expression
# |   107 |   static_assert(test());
# |       |                 ^~~~~~
# | /Users/nguyennhat/Desktop/llvm-2/libcxx/test/support/min_allocator.h:322:85: note: cast from 'const void *' is not allowed in a constant expression because the pointed object type 'min_pointer<MoveOnly>' is not similar to the target type 'const min_pointer<const MoveOnly>'
# |   322 |     TEST_CONSTEXPR_CXX14 explicit min_pointer(min_pointer<const void, ID> p) : ptr_(static_cast<const T*>(p.ptr_)) {}
# |       |                                                                                     ^
# | /Users/nguyennhat/Desktop/llvm-2/build/runtimes/runtimes-bins/libcxx/test-suite-install/include/c++/v1/deque:729:32: note: in call to 'min_pointer({&{*new min_pointer<MoveOnly>[1]#16}[0]})'
# |   729 |     __map_const_pointer __mp = static_cast<__map_const_pointer>(__map_.begin() + __start_ / __block_size);
# |       |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | /Users/nguyennhat/Desktop/llvm-2/build/runtimes/runtimes-bins/libcxx/test-suite-install/include/c++/v1/deque:2570:43: note: in call to '__x.begin()'
# |  2570 |   return __sz == __y.size() && std::equal(__x.begin(), __x.end(), __y.begin());
# |       |                                           ^~~~~~~~~~~
# | /Users/nguyennhat/Desktop/llvm-2/libcxx/test/std/containers/sequences/deque/deque.cons/move_assign.pass.cpp:94:12: note: in call to 'operator==<MoveOnly, min_allocator<MoveOnly>>(c2, c3)'
# |    94 |     assert(c2 == c3);

I opened #136067 for the type issue with const_iterator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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 deque
5 participants