Skip to content

Conversation

@frederick-vs-ja
Copy link
Contributor

@frederick-vs-ja frederick-vs-ja commented Dec 8, 2025

Applying it (adding missing const) to pre-C++20 dispatching mechanism.

  • Also implement LWG3677 for tuple, by modifications to __memory/allocator_arg_t.h.
  • Also partially implement LWG4312 for polymorphic_allocator, by reusing relevant C++20 mechanism changes around allocator_arg_t.
  • Also add fallback dispatching and overloads to emit better error messages.

Need to make std.memory.uses_allocator_construction export std.functional.reference_wrapper to support Clang module builds.

Towards #100256.

@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner December 8, 2025 08:25
@frederick-vs-ja frederick-vs-ja added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Dec 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 8, 2025

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

Changes

Applying it (adding missing `const``) to pre-C++20 dispatching mechanisms.

  • Also partially implement LWG3677 for tuple, by modifications to __memory/allocator_arg_t.h.
  • Also partially implement LWG4312 for polymorphic_allocator, by reusing relevant C++20 mechanism changes around allocator_arg_t.
  • Also add fallback dispatching and overloads to emit better error messages.

Towards #100256.


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

5 Files Affected:

  • (modified) libcxx/include/__memory/allocator_arg_t.h (+5-23)
  • (modified) libcxx/include/__memory/uses_allocator_construction.h (+23-13)
  • (modified) libcxx/include/__memory_resource/polymorphic_allocator.h (+16-33)
  • (modified) libcxx/include/tuple (+10)
  • (modified) libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/construct_piecewise_pair_evil.pass.cpp (+3-3)
diff --git a/libcxx/include/__memory/allocator_arg_t.h b/libcxx/include/__memory/allocator_arg_t.h
index 31a73fc4557ef..a3dac879f45b6 100644
--- a/libcxx/include/__memory/allocator_arg_t.h
+++ b/libcxx/include/__memory/allocator_arg_t.h
@@ -14,6 +14,7 @@
 #include <__memory/uses_allocator.h>
 #include <__type_traits/integral_constant.h>
 #include <__type_traits/is_constructible.h>
+#include <__type_traits/remove_cv.h>
 #include <__type_traits/remove_cvref.h>
 #include <__utility/forward.h>
 
@@ -40,34 +41,15 @@ constexpr allocator_arg_t allocator_arg = allocator_arg_t();
 template <class _Tp, class _Alloc, class... _Args>
 struct __uses_alloc_ctor_imp {
   using _RawAlloc _LIBCPP_NODEBUG = __remove_cvref_t<_Alloc>;
-  static const bool __ua          = uses_allocator<_Tp, _RawAlloc>::value;
-  static const bool __ic          = is_constructible<_Tp, allocator_arg_t, _Alloc, _Args...>::value;
-  static const int value          = __ua ? 2 - __ic : 0;
+  static constexpr bool __ua      = uses_allocator<__remove_cv_t<_Tp>, _RawAlloc>::value;
+  static constexpr bool __ic_head = is_constructible<_Tp, allocator_arg_t, const _RawAlloc&, _Args...>::value;
+  static constexpr bool __ic_tail = is_constructible<_Tp, _Args..., const _RawAlloc&>::value;
+  static constexpr int value      = __ua ? (__ic_head ? 1 : __ic_tail ? 2 : -1) : 0;
 };
 
 template <class _Tp, class _Alloc, class... _Args>
 struct __uses_alloc_ctor : integral_constant<int, __uses_alloc_ctor_imp<_Tp, _Alloc, _Args...>::value> {};
 
-template <class _Tp, class _Allocator, class... _Args>
-inline _LIBCPP_HIDE_FROM_ABI void
-__user_alloc_construct_impl(integral_constant<int, 0>, _Tp* __storage, const _Allocator&, _Args&&... __args) {
-  new (__storage) _Tp(std::forward<_Args>(__args)...);
-}
-
-// FIXME: This should have a version which takes a non-const alloc.
-template <class _Tp, class _Allocator, class... _Args>
-inline _LIBCPP_HIDE_FROM_ABI void
-__user_alloc_construct_impl(integral_constant<int, 1>, _Tp* __storage, const _Allocator& __a, _Args&&... __args) {
-  new (__storage) _Tp(allocator_arg, __a, std::forward<_Args>(__args)...);
-}
-
-// FIXME: This should have a version which takes a non-const alloc.
-template <class _Tp, class _Allocator, class... _Args>
-inline _LIBCPP_HIDE_FROM_ABI void
-__user_alloc_construct_impl(integral_constant<int, 2>, _Tp* __storage, const _Allocator& __a, _Args&&... __args) {
-  new (__storage) _Tp(std::forward<_Args>(__args)..., __a);
-}
-
 #endif // _LIBCPP_CXX03_LANG
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__memory/uses_allocator_construction.h b/libcxx/include/__memory/uses_allocator_construction.h
index 6733f5cf6fc35..ff729015b6668 100644
--- a/libcxx/include/__memory/uses_allocator_construction.h
+++ b/libcxx/include/__memory/uses_allocator_construction.h
@@ -10,10 +10,12 @@
 #define _LIBCPP___MEMORY_USES_ALLOCATOR_CONSTRUCTION_H
 
 #include <__config>
+#include <__functional/reference_wrapper.h>
 #include <__memory/construct_at.h>
 #include <__memory/uses_allocator.h>
 #include <__tuple/tuple_like_no_subrange.h>
 #include <__type_traits/enable_if.h>
+#include <__type_traits/is_constructible.h>
 #include <__type_traits/remove_cv.h>
 #include <__utility/declval.h>
 #include <__utility/pair.h>
@@ -34,23 +36,27 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 template <class _Tp>
 inline constexpr bool __is_cv_std_pair = __is_pair_v<remove_cv_t<_Tp>>;
 
-template <class _Tp, class = void>
+template <class _Tp, bool _IsCvStdPair = __is_cv_std_pair<_Tp>>
 struct __uses_allocator_construction_args;
 
+#  if _LIBCPP_STD_VER >= 20
+template <class _Tp>
+using __uses_allocator_construction_args_recursive = __uses_allocator_construction_args<_Tp>;
+#  else
+template <class _Tp>
+using __uses_allocator_construction_args_recursive = __uses_allocator_construction_args<_Tp, false>;
+#  endif
+
 namespace __uses_allocator_detail {
 
 template <class _Ap, class _Bp>
 void __fun(const pair<_Ap, _Bp>&);
 
+template <class, class = void>
+inline constexpr bool __convertible_to_const_pair_ref = false;
 template <class _Tp>
-decltype(__uses_allocator_detail::__fun(std::declval<_Tp>()), true_type()) __convertible_to_const_pair_ref_impl(int);
-
-template <class>
-false_type __convertible_to_const_pair_ref_impl(...);
-
-template <class _Tp>
-inline constexpr bool __convertible_to_const_pair_ref =
-    decltype(__uses_allocator_detail::__convertible_to_const_pair_ref_impl<_Tp>(0))::value;
+inline constexpr bool
+    __convertible_to_const_pair_ref<_Tp, decltype(__uses_allocator_detail::__fun(std::declval<_Tp>()))> = true;
 
 #  if _LIBCPP_STD_VER >= 23
 template <class _Tp, class _Up>
@@ -67,21 +73,25 @@ template <class _Type, class _Alloc, class... _Args>
 _LIBCPP_HIDE_FROM_ABI constexpr _Type __make_obj_using_allocator(const _Alloc& __alloc, _Args&&... __args);
 
 template <class _Pair>
-struct __uses_allocator_construction_args<_Pair, __enable_if_t<__is_cv_std_pair<_Pair>>> {
+struct __uses_allocator_construction_args<_Pair, true> {
   template <class _Alloc, class _Tuple1, class _Tuple2>
   static _LIBCPP_HIDE_FROM_ABI constexpr auto
   __apply(const _Alloc& __alloc, piecewise_construct_t, _Tuple1&& __x, _Tuple2&& __y) noexcept {
     return std::make_tuple(
+#  if _LIBCPP_STD_VER >= 20
         piecewise_construct,
+#  else
+        std::ref(piecewise_construct),
+#  endif
         std::apply(
             [&__alloc](auto&&... __args1) {
-              return __uses_allocator_construction_args<typename _Pair::first_type>::__apply(
+              return __uses_allocator_construction_args_recursive<typename _Pair::first_type>::__apply(
                   __alloc, std::forward<decltype(__args1)>(__args1)...);
             },
             std::forward<_Tuple1>(__x)),
         std::apply(
             [&__alloc](auto&&... __args2) {
-              return __uses_allocator_construction_args<typename _Pair::second_type>::__apply(
+              return __uses_allocator_construction_args_recursive<typename _Pair::second_type>::__apply(
                   __alloc, std::forward<decltype(__args2)>(__args2)...);
             },
             std::forward<_Tuple2>(__y)));
@@ -170,7 +180,7 @@ struct __uses_allocator_construction_args<_Pair, __enable_if_t<__is_cv_std_pair<
 };
 
 template <class _Type>
-struct __uses_allocator_construction_args<_Type, __enable_if_t<!__is_cv_std_pair<_Type>>> {
+struct __uses_allocator_construction_args<_Type, false> {
   template <class _Alloc, class... _Args>
   static _LIBCPP_HIDE_FROM_ABI constexpr auto __apply(const _Alloc& __alloc, _Args&&... __args) noexcept {
     if constexpr (!uses_allocator_v<remove_cv_t<_Type>, _Alloc> && is_constructible_v<_Type, _Args...>) {
diff --git a/libcxx/include/__memory_resource/polymorphic_allocator.h b/libcxx/include/__memory_resource/polymorphic_allocator.h
index 9a351199b5b16..c3910ca70297a 100644
--- a/libcxx/include/__memory_resource/polymorphic_allocator.h
+++ b/libcxx/include/__memory_resource/polymorphic_allocator.h
@@ -14,9 +14,12 @@
 #include <__cstddef/byte.h>
 #include <__cstddef/max_align_t.h>
 #include <__fwd/pair.h>
+#include <__memory/construct_at.h>
+#include <__memory/uses_allocator_construction.h>
 #include <__memory_resource/memory_resource.h>
 #include <__new/exceptions.h>
 #include <__new/placement_new_delete.h>
+#include <__type_traits/remove_cv.h>
 #include <__utility/exception_guard.h>
 #include <__utility/piecewise_construct.h>
 #include <limits>
@@ -35,6 +38,16 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 namespace pmr {
 
+template <class _Type, class _Alloc, class... _Args>
+_LIBCPP_HIDE_FROM_ABI _Type*
+__uninitialized_construct_using_allocator_nocv(_Type* __ptr, const _Alloc& __alloc, _Args&&... __args) {
+  return std::apply(
+      [__ptr_nocv = const_cast<remove_cv_t<_Type>*>(__ptr)](auto&&... __xs) {
+        return std::__construct_at(__ptr_nocv, std::forward<decltype(__xs)>(__xs)...);
+      },
+      __uses_allocator_construction_args<_Type>::__apply(__alloc, std::forward<_Args>(__args)...));
+}
+
 // [mem.poly.allocator.class]
 
 template <class _ValueType
@@ -122,24 +135,14 @@ class _LIBCPP_AVAILABILITY_PMR polymorphic_allocator {
 
   template <class _Tp, class... _Ts>
   _LIBCPP_HIDE_FROM_ABI void construct(_Tp* __p, _Ts&&... __args) {
-    std::__user_alloc_construct_impl(
-        typename __uses_alloc_ctor<_Tp, polymorphic_allocator&, _Ts...>::type(),
-        __p,
-        *this,
-        std::forward<_Ts>(__args)...);
+    std::pmr::__uninitialized_construct_using_allocator_nocv(__p, *this, std::forward<_Ts>(__args)...);
   }
 
   template <class _T1, class _T2, class... _Args1, class... _Args2>
   _LIBCPP_HIDE_FROM_ABI void
   construct(pair<_T1, _T2>* __p, piecewise_construct_t, tuple<_Args1...> __x, tuple<_Args2...> __y) {
-    ::new ((void*)__p) pair<_T1, _T2>(
-        piecewise_construct,
-        __transform_tuple(typename __uses_alloc_ctor< _T1, polymorphic_allocator&, _Args1... >::type(),
-                          std::move(__x),
-                          make_index_sequence<sizeof...(_Args1)>()),
-        __transform_tuple(typename __uses_alloc_ctor< _T2, polymorphic_allocator&, _Args2... >::type(),
-                          std::move(__y),
-                          make_index_sequence<sizeof...(_Args2)>()));
+    std::pmr::__uninitialized_construct_using_allocator_nocv(
+        __p, *this, piecewise_construct, tuple<_Args1&&...>(std::move(__x)), tuple<_Args2&&...>(std::move(__y)));
   }
 
   template <class _T1, class _T2>
@@ -193,26 +196,6 @@ class _LIBCPP_AVAILABILITY_PMR polymorphic_allocator {
 #  endif
 
 private:
-  template <class... _Args, size_t... _Is>
-  _LIBCPP_HIDE_FROM_ABI tuple<_Args&&...>
-  __transform_tuple(integral_constant<int, 0>, tuple<_Args...>&& __t, index_sequence<_Is...>) {
-    return std::forward_as_tuple(std::get<_Is>(std::move(__t))...);
-  }
-
-  template <class... _Args, size_t... _Is>
-  _LIBCPP_HIDE_FROM_ABI tuple<allocator_arg_t const&, polymorphic_allocator&, _Args&&...>
-  __transform_tuple(integral_constant<int, 1>, tuple<_Args...>&& __t, index_sequence<_Is...>) {
-    using _Tup = tuple<allocator_arg_t const&, polymorphic_allocator&, _Args&&...>;
-    return _Tup(allocator_arg, *this, std::get<_Is>(std::move(__t))...);
-  }
-
-  template <class... _Args, size_t... _Is>
-  _LIBCPP_HIDE_FROM_ABI tuple<_Args&&..., polymorphic_allocator&>
-  __transform_tuple(integral_constant<int, 2>, tuple<_Args...>&& __t, index_sequence<_Is...>) {
-    using _Tup = tuple<_Args&&..., polymorphic_allocator&>;
-    return _Tup(std::get<_Is>(std::move(__t))..., *this);
-  }
-
   _LIBCPP_HIDE_FROM_ABI size_t __max_size() const noexcept {
     return numeric_limits<size_t>::max() / sizeof(value_type);
   }
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 670b90fc7b3b9..7c3b1908c3f39 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -373,6 +373,11 @@ public:
     static_assert(!is_reference<_Hp>::value, "Attempted to default construct a reference element in a tuple");
   }
 
+  template <class _Alloc, class... _Args>
+  _LIBCPP_HIDE_FROM_ABI __tuple_leaf(integral_constant<int, -1>, const _Alloc&, _Args&&...) {
+    static_assert(false, "If uses_allocator_v<T, A> is true, T has to be allocator-constructible");
+  }
+
   template <class _Alloc>
   _LIBCPP_HIDE_FROM_ABI constexpr __tuple_leaf(integral_constant<int, 0>, const _Alloc&) : __value_() {
     static_assert(!is_reference<_Hp>::value, "Attempted to default construct a reference element in a tuple");
@@ -447,6 +452,11 @@ public:
 
   _LIBCPP_HIDE_FROM_ABI constexpr __tuple_leaf() noexcept(is_nothrow_default_constructible<_Hp>::value) {}
 
+  template <class _Alloc, class... _Args>
+  _LIBCPP_HIDE_FROM_ABI __tuple_leaf(integral_constant<int, -1>, const _Alloc&, _Args&&...) {
+    static_assert(false, "If uses_allocator_v<T, A> is true, T has to be allocator-constructible");
+  }
+
   template <class _Alloc>
   _LIBCPP_HIDE_FROM_ABI constexpr __tuple_leaf(integral_constant<int, 0>, const _Alloc&) {}
 
diff --git a/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/construct_piecewise_pair_evil.pass.cpp b/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/construct_piecewise_pair_evil.pass.cpp
index b77734c28e12d..11eff1dc509e7 100644
--- a/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/construct_piecewise_pair_evil.pass.cpp
+++ b/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/construct_piecewise_pair_evil.pass.cpp
@@ -30,10 +30,10 @@ template <class T>
 struct EvilAlloc {
   explicit EvilAlloc() : inner_(std::pmr::null_memory_resource()) {}
 
-  EvilAlloc(std::pmr::polymorphic_allocator<T>& a) : inner_(a) {}
+  EvilAlloc(std::pmr::polymorphic_allocator<T>&) = delete;
   EvilAlloc(std::pmr::polymorphic_allocator<T>&& a) : inner_(a) {}
-  EvilAlloc(std::pmr::polymorphic_allocator<T> const& a)  = delete;
-  EvilAlloc(std::pmr::polymorphic_allocator<T> const&& a) = delete;
+  EvilAlloc(std::pmr::polymorphic_allocator<T> const& a) : inner_(a) {}
+  EvilAlloc(std::pmr::polymorphic_allocator<T> const&&) = delete;
 
   using value_type = T;
   template <class U>

@frederick-vs-ja frederick-vs-ja force-pushed the lwg-3187-polymorphic_allocator branch from 271efc4 to f93a11b Compare December 9, 2025 02:43
Applying it (adding missing `const`) to pre-C++20 dispatching mechanism.

- Also implement LWG3677 for `tuple`, by modifications to
`__memory/allocator_arg_t.h`.
- Also partially implement LWG4312 for `polymorphic_allocator`, by
reusing relevant C++20 mechanism changes around `allocator_arg_t`.
- Also add fallback dispatching and overloads to emit better error
messages.

Making `std.memory.uses_allocator_construction` export
`std.functional.reference_wrapper` to support Clang module builds.
@frederick-vs-ja frederick-vs-ja force-pushed the lwg-3187-polymorphic_allocator branch from f93a11b to 3775e8f Compare December 9, 2025 04:05
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.

2 participants