diff mbox series

C++2a Utility functions to implement uses-allocator construction (P0591R4)

Message ID 20190301135035.GA15423@redhat.com
State New
Headers show
Series C++2a Utility functions to implement uses-allocator construction (P0591R4) | expand

Commit Message

Jonathan Wakely March 1, 2019, 1:50 p.m. UTC
* include/std/memory (uses_allocator_construction_args): New set of
	overloaded functions.
	(make_obj_using_allocator, uninitialized_construct_using_allocator):
	New functions.
	* include/std/memory_resource (polymorphic_allocator::construct)
	[__cplusplus > 201703l]: Replace all overloads with a single function
	using uses_allocator_construction_args.
	* testsuite/20_util/polymorphic_allocator/construct_c++2a.cc: New
	test.
	* testsuite/20_util/uses_allocator/make_obj.cc: New test.

Tested powerpc64le-linux, committed to trunk.
commit e2d0fdcde8a7dcebfab8246372d3756c5c489717
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri Mar 1 13:27:58 2019 +0000

    C++2a Utility functions to implement uses-allocator construction (P0591R4)
    
            * include/std/memory (uses_allocator_construction_args): New set of
            overloaded functions.
            (make_obj_using_allocator, uninitialized_construct_using_allocator):
            New functions.
            * include/std/memory_resource (polymorphic_allocator::construct)
            [__cplusplus > 201703l]: Replace all overloads with a single function
            using uses_allocator_construction_args.
            * testsuite/20_util/polymorphic_allocator/construct_c++2a.cc: New
            test.
            * testsuite/20_util/uses_allocator/make_obj.cc: New test.

Comments

Jonathan Wakely March 1, 2019, 2:06 p.m. UTC | #1
On 01/03/19 13:50 +0000, Jonathan Wakely wrote:
>	* include/std/memory (uses_allocator_construction_args): New set of
>	overloaded functions.
>	(make_obj_using_allocator, uninitialized_construct_using_allocator):
>	New functions.
>	* include/std/memory_resource (polymorphic_allocator::construct)
>	[__cplusplus > 201703l]: Replace all overloads with a single function
>	using uses_allocator_construction_args.
>	* testsuite/20_util/polymorphic_allocator/construct_c++2a.cc: New
>	test.
>	* testsuite/20_util/uses_allocator/make_obj.cc: New test.

If we don't care about providing the exact signatures from the C++2a
draft, we could do this and use it in C++17 as well ...
diff --git a/libstdc++-v3/include/std/memory b/libstdc++-v3/include/std/memory
index 00a85eef25e..045974a1b46 100644
--- a/libstdc++-v3/include/std/memory
+++ b/libstdc++-v3/include/std/memory
@@ -168,7 +168,7 @@ get_pointer_safety() noexcept { return pointer_safety::relaxed; }
     }
 #endif // C++2a
 
-#if __cplusplus > 201703L
+#if __cplusplus >= 201703L
   template<typename _Tp>
     struct __is_pair : false_type { };
   template<typename _Tp, typename _Up>
@@ -176,167 +176,94 @@ get_pointer_safety() noexcept { return pointer_safety::relaxed; }
   template<typename _Tp, typename _Up>
     struct __is_pair<const pair<_Tp, _Up>> : true_type { };
 
-  template<typename _Tp, typename __ = _Require<__not_<__is_pair<_Tp>>>,
-	   typename _Alloc, typename... _Args>
+  // Equivalent of uses_allocator_construction_args for internal use in C++17
+  template<typename _Tp, typename _Alloc, typename... _Args>
     constexpr auto
     __uses_alloc_args(const _Alloc& __a, _Args&&... __args) noexcept
     {
-      if constexpr (uses_allocator_v<remove_cv_t<_Tp>, _Alloc>)
+      if constexpr (!__is_pair<_Tp>::value)
 	{
-	  if constexpr (is_constructible_v<_Tp, allocator_arg_t,
-					   const _Alloc&, _Args...>)
+	  if constexpr (uses_allocator_v<remove_cv_t<_Tp>, _Alloc>)
 	    {
-	      return tuple<allocator_arg_t, const _Alloc&, _Args&&...>(
-		  allocator_arg, __a, std::forward<_Args>(__args)...);
+	      if constexpr (is_constructible_v<_Tp, allocator_arg_t,
+		  const _Alloc&, _Args...>)
+		{
+		  return tuple<allocator_arg_t, const _Alloc&, _Args&&...>(
+		      allocator_arg, __a, std::forward<_Args>(__args)...);
+		}
+	      else
+		{
+		  static_assert(
+		      is_constructible_v<_Tp, _Args..., const _Alloc&>);
+
+		  return tuple<_Args&&..., const _Alloc&>(
+		      std::forward<_Args>(__args)..., __a);
+		}
 	    }
 	  else
 	    {
-	      static_assert(is_constructible_v<_Tp, _Args..., const _Alloc&>);
+	      static_assert(is_constructible_v<_Tp, _Args...>);
 
-	      return tuple<_Args&&..., const _Alloc&>(
-		  std::forward<_Args>(__args)..., __a);
+	      return tuple<_Args&&...>(std::forward<_Args>(__args)...);
 	    }
 	}
       else
 	{
-	  static_assert(is_constructible_v<_Tp, _Args...>);
-
-	  return tuple<_Args&&...>(std::forward<_Args>(__args)...);
-	}
-    }
-
-#if __cpp_concepts
-  template<typename _Tp>
-    concept bool _Std_pair = __is_pair<_Tp>::value;
-#endif
-
-// This is a temporary workaround until -fconcepts is implied by -std=gnu++2a
-#if __cpp_concepts
-# define _GLIBCXX_STD_PAIR_CONSTRAINT(T) _Std_pair T
-# define _GLIBCXX_STD_PAIR_CONSTRAINT_(T) _Std_pair T
-#else
-# define _GLIBCXX_STD_PAIR_CONSTRAINT(T) \
-      typename T, typename __ = _Require<__is_pair<T>>
-# define _GLIBCXX_STD_PAIR_CONSTRAINT_(T) typename T, typename
-#endif
-
-  template<typename _Tp,
-#if ! __cpp_concepts
-	   typename __ = _Require<__not_<__is_pair<_Tp>>>,
-#endif
-	   typename _Alloc, typename... _Args>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a,
-				     _Args&&... __args) noexcept
-#if __cpp_concepts
-    requires ! _Std_pair<_Tp>
-#endif
-    {
-      return std::__uses_alloc_args<_Tp>(__a, std::forward<_Args>(__args)...);
-    }
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Tuple1, typename _Tuple2>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
-				     _Tuple1&& __x, _Tuple2&& __y) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&, _Up&&, _Vp&&) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&,
-				     const pair<_Up, _Vp>&) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&, pair<_Up, _Vp>&&) noexcept;
+	  static_assert(sizeof...(__args) <= 3);
 
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Tuple1, typename _Tuple2>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
-				     _Tuple1&& __x, _Tuple2&& __y) noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::apply([&__a](auto&&... __args1) {
-	      return std::uses_allocator_construction_args<_Tp1>(
-		  __a, std::forward<decltype(__args1)>(__args1)...);
-	  }, std::forward<_Tuple1>(__x)),
-	  std::apply([&__a](auto&&... __args2) {
-	      return std::uses_allocator_construction_args<_Tp2>(
-		  __a, std::forward<decltype(__args2)>(__args2)...);
-	  }, std::forward<_Tuple2>(__y)));
-    }
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a) noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a),
-	  std::uses_allocator_construction_args<_Tp2>(__a));
-    }
+	  using _Tp1 = typename _Tp::first_type;
+	  using _Tp2 = typename _Tp::second_type;
 
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a, _Up&& __u, _Vp&& __v)
-      noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a,
-	    std::forward<_Up>(__u)),
-	  std::uses_allocator_construction_args<_Tp2>(__a,
-	    std::forward<_Vp>(__v)));
-    }
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a,
-				     const pair<_Up, _Vp>& __pr) noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a, __pr.first),
-	  std::uses_allocator_construction_args<_Tp2>(__a, __pr.second));
+	  if constexpr (sizeof...(__args) == 0)
+	    {
+	      return std::make_tuple(piecewise_construct,
+		  std::__uses_alloc_args<_Tp1>(__a),
+		  std::__uses_alloc_args<_Tp2>(__a));
+	    }
+	  else if constexpr (sizeof...(__args) == 1)
+	    {
+	      return std::make_tuple(piecewise_construct,
+		  std::__uses_alloc_args<_Tp1>(__a,
+		    std::forward<_Args>(__args).first...),
+		  std::__uses_alloc_args<_Tp2>(__a,
+		    std::forward<_Args>(__args).second...));
+	    }
+	  else if constexpr (sizeof...(__args) == 2)
+	    {
+	      return [&](auto&& __arg1, auto&& __arg2)
+		{
+		  return std::make_tuple(piecewise_construct,
+		      std::__uses_alloc_args<_Tp1>(__a,
+			std::forward<decltype(__arg1)>(__arg1)),
+		      std::__uses_alloc_args<_Tp2>(__a,
+			std::forward<decltype(__arg2)>(__arg2)));
+		}(std::forward<_Args>(__args)...);
+	    }
+	  else if constexpr (sizeof...(__args) == 3)
+	    {
+	      return [&](piecewise_construct_t, auto&& __arg1, auto&& __arg2)
+		{
+		  return std::make_tuple(piecewise_construct,
+		      std::apply([&](auto&&... __args1) {
+			  return std::__uses_alloc_args<_Tp1>(__a,
+			      std::forward<decltype(__args1)>(__args1)...);
+		      }, std::forward<decltype(__arg1)>(__arg1)),
+		      std::apply([&](auto&&... __args2) {
+			  return std::__uses_alloc_args<_Tp2>(__a,
+			      std::forward<decltype(__args2)>(__args2)...);
+		      }, std::forward<decltype(__arg2)>(__arg2)));
+		}(std::forward<_Args>(__args)...);
+	    }
+	}
     }
 
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
+#if __cplusplus > 201703L
+  template<typename _Tp, typename _Alloc, typename... _Args>
     constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a,
-				     pair<_Up, _Vp>&& __pr) noexcept
+    uses_allocator_construction_args(const _Alloc& __a, _Args&&... __args)
     {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a,
-	    std::move(__pr).first),
-	  std::uses_allocator_construction_args<_Tp2>(__a,
-	    std::move(__pr).second));
+      return std::__uses_alloc_args<_Tp>(__a,
+	  std::forward<_Args>(__args)...);
     }
 
   template<typename _Tp, typename _Alloc, typename... _Args>
@@ -356,8 +283,8 @@ get_pointer_safety() noexcept { return pointer_safety::relaxed; }
       return ::new(__vp) _Tp(std::make_obj_using_allocator<_Tp>(__a,
 	    std::forward<_Args>(__args)...));
     }
-
 #endif // C++2a
+#endif // C++17
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
diff --git a/libstdc++-v3/include/std/memory_resource b/libstdc++-v3/include/std/memory_resource
index a212bccc9b1..3ea6990ecae 100644
--- a/libstdc++-v3/include/std/memory_resource
+++ b/libstdc++-v3/include/std/memory_resource
@@ -124,14 +124,6 @@ namespace pmr
   template<typename _Tp>
     class polymorphic_allocator
     {
-      // _GLIBCXX_RESOLVE_LIB_DEFECTS
-      // 2975. Missing case for pair construction in polymorphic allocators
-      template<typename _Up>
-	struct __not_pair { using type = void; };
-
-      template<typename _Up1, typename _Up2>
-	struct __not_pair<pair<_Up1, _Up2>> { };
-
     public:
       using value_type = _Tp;
 
@@ -170,89 +162,15 @@ namespace pmr
       __attribute__((__nonnull__))
       { _M_resource->deallocate(__p, __n * sizeof(_Tp), alignof(_Tp)); }
 
-#if __cplusplus <= 201703L
-      template<typename _Tp1, typename... _Args>
-	__attribute__((__nonnull__))
-	typename __not_pair<_Tp1>::type
-	construct(_Tp1* __p, _Args&&... __args)
-	{
-	  // _GLIBCXX_RESOLVE_LIB_DEFECTS
-	  // 2969. polymorphic_allocator::construct() shouldn't pass resource()
-	  using __use_tag
-	    = std::__uses_alloc_t<_Tp1, polymorphic_allocator, _Args...>;
-	  if constexpr (is_base_of_v<__uses_alloc0, __use_tag>)
-	    ::new(__p) _Tp1(std::forward<_Args>(__args)...);
-	  else if constexpr (is_base_of_v<__uses_alloc1_, __use_tag>)
-	    ::new(__p) _Tp1(allocator_arg, *this,
-			    std::forward<_Args>(__args)...);
-	  else
-	    ::new(__p) _Tp1(std::forward<_Args>(__args)..., *this);
-	}
-
-      template<typename _Tp1, typename _Tp2,
-	       typename... _Args1, typename... _Args2>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, piecewise_construct_t,
-		  tuple<_Args1...> __x, tuple<_Args2...> __y)
-	{
-	  auto __x_tag =
-	    __use_alloc<_Tp1, polymorphic_allocator, _Args1...>(*this);
-	  auto __y_tag =
-	    __use_alloc<_Tp2, polymorphic_allocator, _Args2...>(*this);
-	  index_sequence_for<_Args1...> __x_i;
-	  index_sequence_for<_Args2...> __y_i;
-
-	  ::new(__p) pair<_Tp1, _Tp2>(piecewise_construct,
-				      _S_construct_p(__x_tag, __x_i, __x),
-				      _S_construct_p(__y_tag, __y_i, __y));
-	}
-
-      template<typename _Tp1, typename _Tp2>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p)
-	{ this->construct(__p, piecewise_construct, tuple<>(), tuple<>()); }
-
-      template<typename _Tp1, typename _Tp2, typename _Up, typename _Vp>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, _Up&& __x, _Vp&& __y)
-	{
-	  this->construct(__p, piecewise_construct,
-			  forward_as_tuple(std::forward<_Up>(__x)),
-			  forward_as_tuple(std::forward<_Vp>(__y)));
-	}
-
-      template <typename _Tp1, typename _Tp2, typename _Up, typename _Vp>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, const std::pair<_Up, _Vp>& __pr)
-	{
-	  this->construct(__p, piecewise_construct,
-			  forward_as_tuple(__pr.first),
-			  forward_as_tuple(__pr.second));
-	}
-
-      template<typename _Tp1, typename _Tp2, typename _Up, typename _Vp>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, pair<_Up, _Vp>&& __pr)
-	{
-	  this->construct(__p, piecewise_construct,
-			  forward_as_tuple(std::forward<_Up>(__pr.first)),
-			  forward_as_tuple(std::forward<_Vp>(__pr.second)));
-	}
-#else
       template<typename _Tp1, typename... _Args>
 	__attribute__((__nonnull__))
 	void
 	construct(_Tp1* __p, _Args&&... __args)
 	{
-	  std::uninitialized_construct_using_allocator(__p, *this,
-	      std::forward<_Args>(__args)...);
+	  ::new ((void*)__p) _Tp1(std::make_from_tuple<_Tp1>(
+		std::__uses_alloc_args<_Tp1>(*this,
+		  std::forward<_Args>(__args)...)));
 	}
-#endif
 
       template<typename _Up>
 	__attribute__((__nonnull__))
@@ -270,30 +188,6 @@ namespace pmr
       { return _M_resource; }
 
     private:
-      using __uses_alloc1_ = __uses_alloc1<polymorphic_allocator>;
-      using __uses_alloc2_ = __uses_alloc2<polymorphic_allocator>;
-
-      template<typename _Ind, typename... _Args>
-	static tuple<_Args&&...>
-	_S_construct_p(__uses_alloc0, _Ind, tuple<_Args...>& __t)
-	{ return std::move(__t); }
-
-      template<size_t... _Ind, typename... _Args>
-	static tuple<allocator_arg_t, polymorphic_allocator, _Args&&...>
-	_S_construct_p(__uses_alloc1_ __ua, index_sequence<_Ind...>,
-		       tuple<_Args...>& __t)
-	{
-	  return {
-	      allocator_arg, *__ua._M_a, std::get<_Ind>(std::move(__t))...
-	  };
-	}
-
-      template<size_t... _Ind, typename... _Args>
-	static tuple<_Args&&..., polymorphic_allocator>
-	_S_construct_p(__uses_alloc2_ __ua, index_sequence<_Ind...>,
-		       tuple<_Args...>& __t)
-	{ return { std::get<_Ind>(std::move(__t))..., *__ua._M_a }; }
-
       memory_resource* _M_resource;
     };
Jonathan Wakely March 1, 2019, 2:33 p.m. UTC | #2
On 01/03/19 14:06 +0000, Jonathan Wakely wrote:
>+	  if constexpr (sizeof...(__args) == 0)
>+	    {
>+	      return std::make_tuple(piecewise_construct,
>+		  std::__uses_alloc_args<_Tp1>(__a),
>+		  std::__uses_alloc_args<_Tp2>(__a));
>+	    }
>+	  else if constexpr (sizeof...(__args) == 1)
>+	    {

If we want to ensure we only accept std::pair and not other pair-like
types, then this branch would need:

              static_assert((__is_pair<_Args>::value && ...));

>+	      return std::make_tuple(piecewise_construct,
>+		  std::__uses_alloc_args<_Tp1>(__a,
>+		    std::forward<_Args>(__args).first...),
>+		  std::__uses_alloc_args<_Tp2>(__a,
>+		    std::forward<_Args>(__args).second...));
>+	    }
>+	  else if constexpr (sizeof...(__args) == 2)
Jonathan Wakely March 4, 2019, 9:14 a.m. UTC | #3
On 01/03/19 14:06 +0000, Jonathan Wakely wrote:
>On 01/03/19 13:50 +0000, Jonathan Wakely wrote:
>>	* include/std/memory (uses_allocator_construction_args): New set of
>>	overloaded functions.
>>	(make_obj_using_allocator, uninitialized_construct_using_allocator):
>>	New functions.
>>	* include/std/memory_resource (polymorphic_allocator::construct)
>>	[__cplusplus > 201703l]: Replace all overloads with a single function
>>	using uses_allocator_construction_args.
>>	* testsuite/20_util/polymorphic_allocator/construct_c++2a.cc: New
>>	test.
>>	* testsuite/20_util/uses_allocator/make_obj.cc: New test.
>
>If we don't care about providing the exact signatures from the C++2a
>draft, we could do this and use it in C++17 as well ...

[...]

>+	  if constexpr (sizeof...(__args) == 0)
>+	    {
>+	      return std::make_tuple(piecewise_construct,
>+		  std::__uses_alloc_args<_Tp1>(__a),
>+		  std::__uses_alloc_args<_Tp2>(__a));
>+	    }
>+	  else if constexpr (sizeof...(__args) == 1)
>+	    {
>+	      return std::make_tuple(piecewise_construct,
>+		  std::__uses_alloc_args<_Tp1>(__a,
>+		    std::forward<_Args>(__args).first...),
>+		  std::__uses_alloc_args<_Tp2>(__a,
>+		    std::forward<_Args>(__args).second...));
>+	    }
>+	  else if constexpr (sizeof...(__args) == 2)
>+	    {
>+	      return [&](auto&& __arg1, auto&& __arg2)
>+		{
>+		  return std::make_tuple(piecewise_construct,
>+		      std::__uses_alloc_args<_Tp1>(__a,
>+			std::forward<decltype(__arg1)>(__arg1)),
>+		      std::__uses_alloc_args<_Tp2>(__a,
>+			std::forward<decltype(__arg2)>(__arg2)));
>+		}(std::forward<_Args>(__args)...);
>+	    }

I tried replacing this lambda with:

	  using _Targs = tuple<_Args&&...>;
	  _Targs __targs{std::forward<_Args>(__args)...};

          using _Args_0 = tuple_element_t<0, _Targs>;
          using _Args_1 = tuple_element_t<1, _Targs>;

          return std::make_tuple(piecewise_construct,
              std::__uses_alloc_args<_Tp1>(__a,
                std::forward<_Args_0>(std::get<0>(__targs))),
              std::__uses_alloc_args<_Tp2>(__a,
                std::forward<_Args_1>(std::get<1>(__targs))));

And similarly for the sizeof...(__args))==3 case.  Which seems more
straightforward, unfortunately it compiles measurably slower, using
more memory. The optimized code is the same size, but unoptimized the
lambda version is a bit smaller.

The current code on trunk compiles fastest, by quite a big margin.
That surprises me as I thought a single function using if-constexpr
would outperform several overloads constrained via SFINAE.

Being able to use __uses_alloc_args in C++17 might be worth the extra
compile-time cost though. I'll keep thinking about it.
Jonathan Wakely March 4, 2019, 9:23 a.m. UTC | #4
On 04/03/19 09:14 +0000, Jonathan Wakely wrote:
>On 01/03/19 14:06 +0000, Jonathan Wakely wrote:
>>On 01/03/19 13:50 +0000, Jonathan Wakely wrote:
>>>	* include/std/memory (uses_allocator_construction_args): New set of
>>>	overloaded functions.
>>>	(make_obj_using_allocator, uninitialized_construct_using_allocator):
>>>	New functions.
>>>	* include/std/memory_resource (polymorphic_allocator::construct)
>>>	[__cplusplus > 201703l]: Replace all overloads with a single function
>>>	using uses_allocator_construction_args.
>>>	* testsuite/20_util/polymorphic_allocator/construct_c++2a.cc: New
>>>	test.
>>>	* testsuite/20_util/uses_allocator/make_obj.cc: New test.
>>
>>If we don't care about providing the exact signatures from the C++2a
>>draft, we could do this and use it in C++17 as well ...
>
>[...]
>
>>+	  if constexpr (sizeof...(__args) == 0)
>>+	    {
>>+	      return std::make_tuple(piecewise_construct,
>>+		  std::__uses_alloc_args<_Tp1>(__a),
>>+		  std::__uses_alloc_args<_Tp2>(__a));
>>+	    }
>>+	  else if constexpr (sizeof...(__args) == 1)
>>+	    {
>>+	      return std::make_tuple(piecewise_construct,
>>+		  std::__uses_alloc_args<_Tp1>(__a,
>>+		    std::forward<_Args>(__args).first...),
>>+		  std::__uses_alloc_args<_Tp2>(__a,
>>+		    std::forward<_Args>(__args).second...));
>>+	    }
>>+	  else if constexpr (sizeof...(__args) == 2)
>>+	    {
>>+	      return [&](auto&& __arg1, auto&& __arg2)
>>+		{
>>+		  return std::make_tuple(piecewise_construct,
>>+		      std::__uses_alloc_args<_Tp1>(__a,
>>+			std::forward<decltype(__arg1)>(__arg1)),
>>+		      std::__uses_alloc_args<_Tp2>(__a,
>>+			std::forward<decltype(__arg2)>(__arg2)));
>>+		}(std::forward<_Args>(__args)...);
>>+	    }
>
>I tried replacing this lambda with:
>
>	  using _Targs = tuple<_Args&&...>;
>	  _Targs __targs{std::forward<_Args>(__args)...};
>
>         using _Args_0 = tuple_element_t<0, _Targs>;
>         using _Args_1 = tuple_element_t<1, _Targs>;
>
>         return std::make_tuple(piecewise_construct,
>             std::__uses_alloc_args<_Tp1>(__a,
>               std::forward<_Args_0>(std::get<0>(__targs))),
>             std::__uses_alloc_args<_Tp2>(__a,
>               std::forward<_Args_1>(std::get<1>(__targs))));
>
>And similarly for the sizeof...(__args))==3 case.  Which seems more
>straightforward, unfortunately it compiles measurably slower, using
>more memory. The optimized code is the same size, but unoptimized the
>lambda version is a bit smaller.
>
>The current code on trunk compiles fastest, by quite a big margin.
>That surprises me as I thought a single function using if-constexpr
>would outperform several overloads constrained via SFINAE.
>
>Being able to use __uses_alloc_args in C++17 might be worth the extra
>compile-time cost though. I'll keep thinking about it.

In case anybody wants to try it out, here's the complete patch
(relative to r269312 on trunk) using std::get<N> to extract elements
from the pack, instead of using lambdas.
diff --git a/libstdc++-v3/include/std/memory b/libstdc++-v3/include/std/memory
index 00a85eef25e..d96dd02b6df 100644
--- a/libstdc++-v3/include/std/memory
+++ b/libstdc++-v3/include/std/memory
@@ -168,7 +168,7 @@ get_pointer_safety() noexcept { return pointer_safety::relaxed; }
     }
 #endif // C++2a
 
-#if __cplusplus > 201703L
+#if __cplusplus >= 201703L
   template<typename _Tp>
     struct __is_pair : false_type { };
   template<typename _Tp, typename _Up>
@@ -176,174 +176,111 @@ get_pointer_safety() noexcept { return pointer_safety::relaxed; }
   template<typename _Tp, typename _Up>
     struct __is_pair<const pair<_Tp, _Up>> : true_type { };
 
-  template<typename _Tp, typename __ = _Require<__not_<__is_pair<_Tp>>>,
-	   typename _Alloc, typename... _Args>
+  // Equivalent of uses_allocator_construction_args for internal use in C++17
+  template<typename _Tp, typename _Alloc, typename... _Args>
     constexpr auto
     __uses_alloc_args(const _Alloc& __a, _Args&&... __args) noexcept
     {
-      if constexpr (uses_allocator_v<remove_cv_t<_Tp>, _Alloc>)
+      if constexpr (!__is_pair<_Tp>::value)
 	{
-	  if constexpr (is_constructible_v<_Tp, allocator_arg_t,
-					   const _Alloc&, _Args...>)
+	  if constexpr (uses_allocator_v<remove_cv_t<_Tp>, _Alloc>)
 	    {
-	      return tuple<allocator_arg_t, const _Alloc&, _Args&&...>(
-		  allocator_arg, __a, std::forward<_Args>(__args)...);
+	      if constexpr (is_constructible_v<_Tp, allocator_arg_t,
+		  const _Alloc&, _Args...>)
+		{
+		  return tuple<allocator_arg_t, const _Alloc&, _Args&&...>(
+		      allocator_arg, __a, std::forward<_Args>(__args)...);
+		}
+	      else
+		{
+		  static_assert(
+		      is_constructible_v<_Tp, _Args..., const _Alloc&>);
+
+		  return tuple<_Args&&..., const _Alloc&>(
+		      std::forward<_Args>(__args)..., __a);
+		}
 	    }
 	  else
 	    {
-	      static_assert(is_constructible_v<_Tp, _Args..., const _Alloc&>);
+	      static_assert(is_constructible_v<_Tp, _Args...>);
 
-	      return tuple<_Args&&..., const _Alloc&>(
-		  std::forward<_Args>(__args)..., __a);
+	      return tuple<_Args&&...>(std::forward<_Args>(__args)...);
 	    }
 	}
       else
 	{
-	  static_assert(is_constructible_v<_Tp, _Args...>);
+	  static_assert(sizeof...(__args) <= 3);
 
-	  return tuple<_Args&&...>(std::forward<_Args>(__args)...);
-	}
-    }
+	  using _Tp1 = typename _Tp::first_type;
+	  using _Tp2 = typename _Tp::second_type;
 
-#if __cpp_concepts
-  template<typename _Tp>
-    concept bool _Std_pair = __is_pair<_Tp>::value;
-#endif
+	  using _Targs = tuple<_Args&&...>;
+	  _Targs __targs{std::forward<_Args>(__args)...};
 
-// This is a temporary workaround until -fconcepts is implied by -std=gnu++2a
-#if __cpp_concepts
-# define _GLIBCXX_STD_PAIR_CONSTRAINT(T) _Std_pair T
-# define _GLIBCXX_STD_PAIR_CONSTRAINT_(T) _Std_pair T
-#else
-# define _GLIBCXX_STD_PAIR_CONSTRAINT(T) \
-      typename T, typename __ = _Require<__is_pair<T>>
-# define _GLIBCXX_STD_PAIR_CONSTRAINT_(T) typename T, typename
-#endif
-
-  template<typename _Tp,
-#if ! __cpp_concepts
-	   typename __ = _Require<__not_<__is_pair<_Tp>>>,
-#endif
-	   typename _Alloc, typename... _Args>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a,
-				     _Args&&... __args) noexcept
-#if __cpp_concepts
-    requires ! _Std_pair<_Tp>
+	  if constexpr (sizeof...(__args) == 0)
+	    {
+	      return std::make_tuple(piecewise_construct,
+		  std::__uses_alloc_args<_Tp1>(__a),
+		  std::__uses_alloc_args<_Tp2>(__a));
+	    }
+	  else if constexpr (sizeof...(__args) == 1)
+	    {
+	      using _Args_0 = tuple_element_t<0, _Targs>;
+#ifdef __STRICT_ANSI__
+	      static_assert(__is_pair<__remove_cvref_t<_Args_0>>::value);
 #endif
-    {
-      return std::__uses_alloc_args<_Tp>(__a, std::forward<_Args>(__args)...);
-    }
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Tuple1, typename _Tuple2>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
-				     _Tuple1&& __x, _Tuple2&& __y) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&, _Up&&, _Vp&&) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&,
-				     const pair<_Up, _Vp>&) noexcept;
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc&, pair<_Up, _Vp>&&) noexcept;
 
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Tuple1, typename _Tuple2>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
-				     _Tuple1&& __x, _Tuple2&& __y) noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::apply([&__a](auto&&... __args1) {
-	      return std::uses_allocator_construction_args<_Tp1>(
-		  __a, std::forward<decltype(__args1)>(__args1)...);
-	  }, std::forward<_Tuple1>(__x)),
-	  std::apply([&__a](auto&&... __args2) {
-	      return std::uses_allocator_construction_args<_Tp2>(
-		  __a, std::forward<decltype(__args2)>(__args2)...);
-	  }, std::forward<_Tuple2>(__y)));
-    }
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a) noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a),
-	  std::uses_allocator_construction_args<_Tp2>(__a));
-    }
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a, _Up&& __u, _Vp&& __v)
-      noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a,
-	    std::forward<_Up>(__u)),
-	  std::uses_allocator_construction_args<_Tp2>(__a,
-	    std::forward<_Vp>(__v)));
-    }
-
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
-    constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a,
-				     const pair<_Up, _Vp>& __pr) noexcept
-    {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a, __pr.first),
-	  std::uses_allocator_construction_args<_Tp2>(__a, __pr.second));
+	      return std::make_tuple(piecewise_construct,
+		  std::__uses_alloc_args<_Tp1>(__a,
+		    std::forward<_Args_0>(std::get<0>(__targs)).first),
+		  std::__uses_alloc_args<_Tp2>(__a,
+		    std::forward<_Args_0>(std::get<0>(__targs)).second));
+	    }
+	  else if constexpr (sizeof...(__args) == 2)
+	    {
+	      using _Args_0 = tuple_element_t<0, _Targs>;
+	      using _Args_1 = tuple_element_t<1, _Targs>;
+
+	      return std::make_tuple(piecewise_construct,
+		  std::__uses_alloc_args<_Tp1>(__a,
+		    std::forward<_Args_0>(std::get<0>(__targs))),
+		  std::__uses_alloc_args<_Tp2>(__a,
+		    std::forward<_Args_1>(std::get<1>(__targs))));
+	    }
+	  else if constexpr (sizeof...(__args) == 3)
+	    {
+	      using _Args_0 = __remove_cvref_t<tuple_element_t<0, _Targs>>;
+	      static_assert(is_same_v<_Args_0, piecewise_construct_t>);
+	      using _Args_1 = tuple_element_t<1, _Targs>;
+	      using _Args_2 = tuple_element_t<2, _Targs>;
+
+	      return std::make_tuple(piecewise_construct,
+		  std::apply([&](auto&&... __args1) {
+		      return std::__uses_alloc_args<_Tp1>(__a,
+			  std::forward<decltype(__args1)>(__args1)...);
+		  }, std::forward<_Args_1>(std::get<1>(__targs))),
+		  std::apply([&](auto&&... __args2) {
+		      return std::__uses_alloc_args<_Tp2>(__a,
+			  std::forward<decltype(__args2)>(__args2)...);
+		  }, std::forward<_Args_2>(std::get<2>(__targs))));
+	    }
+	}
     }
 
-  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
-	   typename _Up, typename _Vp>
+#if __cplusplus > 201703L
+  template<typename _Tp, typename _Alloc, typename... _Args>
     constexpr auto
-    uses_allocator_construction_args(const _Alloc& __a,
-				     pair<_Up, _Vp>&& __pr) noexcept
+    uses_allocator_construction_args(const _Alloc& __a, _Args&&... __args)
     {
-      using _Tp1 = typename _Tp::first_type;
-      using _Tp2 = typename _Tp::second_type;
-
-      return std::make_tuple(piecewise_construct,
-	  std::uses_allocator_construction_args<_Tp1>(__a,
-	    std::move(__pr).first),
-	  std::uses_allocator_construction_args<_Tp2>(__a,
-	    std::move(__pr).second));
+      return std::__uses_alloc_args<_Tp>(__a,
+	  std::forward<_Args>(__args)...);
     }
 
   template<typename _Tp, typename _Alloc, typename... _Args>
     inline _Tp
     make_obj_using_allocator(const _Alloc& __a, _Args&&... __args)
     {
-      return std::make_from_tuple<_Tp>(uses_allocator_construction_args<_Tp>(
+      return std::make_from_tuple<_Tp>(__uses_alloc_args<_Tp>(
 	    __a, std::forward<_Args>(__args)...));
     }
 
@@ -356,8 +293,8 @@ get_pointer_safety() noexcept { return pointer_safety::relaxed; }
       return ::new(__vp) _Tp(std::make_obj_using_allocator<_Tp>(__a,
 	    std::forward<_Args>(__args)...));
     }
-
 #endif // C++2a
+#endif // C++17
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
diff --git a/libstdc++-v3/include/std/memory_resource b/libstdc++-v3/include/std/memory_resource
index a212bccc9b1..3ea6990ecae 100644
--- a/libstdc++-v3/include/std/memory_resource
+++ b/libstdc++-v3/include/std/memory_resource
@@ -124,14 +124,6 @@ namespace pmr
   template<typename _Tp>
     class polymorphic_allocator
     {
-      // _GLIBCXX_RESOLVE_LIB_DEFECTS
-      // 2975. Missing case for pair construction in polymorphic allocators
-      template<typename _Up>
-	struct __not_pair { using type = void; };
-
-      template<typename _Up1, typename _Up2>
-	struct __not_pair<pair<_Up1, _Up2>> { };
-
     public:
       using value_type = _Tp;
 
@@ -170,89 +162,15 @@ namespace pmr
       __attribute__((__nonnull__))
       { _M_resource->deallocate(__p, __n * sizeof(_Tp), alignof(_Tp)); }
 
-#if __cplusplus <= 201703L
-      template<typename _Tp1, typename... _Args>
-	__attribute__((__nonnull__))
-	typename __not_pair<_Tp1>::type
-	construct(_Tp1* __p, _Args&&... __args)
-	{
-	  // _GLIBCXX_RESOLVE_LIB_DEFECTS
-	  // 2969. polymorphic_allocator::construct() shouldn't pass resource()
-	  using __use_tag
-	    = std::__uses_alloc_t<_Tp1, polymorphic_allocator, _Args...>;
-	  if constexpr (is_base_of_v<__uses_alloc0, __use_tag>)
-	    ::new(__p) _Tp1(std::forward<_Args>(__args)...);
-	  else if constexpr (is_base_of_v<__uses_alloc1_, __use_tag>)
-	    ::new(__p) _Tp1(allocator_arg, *this,
-			    std::forward<_Args>(__args)...);
-	  else
-	    ::new(__p) _Tp1(std::forward<_Args>(__args)..., *this);
-	}
-
-      template<typename _Tp1, typename _Tp2,
-	       typename... _Args1, typename... _Args2>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, piecewise_construct_t,
-		  tuple<_Args1...> __x, tuple<_Args2...> __y)
-	{
-	  auto __x_tag =
-	    __use_alloc<_Tp1, polymorphic_allocator, _Args1...>(*this);
-	  auto __y_tag =
-	    __use_alloc<_Tp2, polymorphic_allocator, _Args2...>(*this);
-	  index_sequence_for<_Args1...> __x_i;
-	  index_sequence_for<_Args2...> __y_i;
-
-	  ::new(__p) pair<_Tp1, _Tp2>(piecewise_construct,
-				      _S_construct_p(__x_tag, __x_i, __x),
-				      _S_construct_p(__y_tag, __y_i, __y));
-	}
-
-      template<typename _Tp1, typename _Tp2>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p)
-	{ this->construct(__p, piecewise_construct, tuple<>(), tuple<>()); }
-
-      template<typename _Tp1, typename _Tp2, typename _Up, typename _Vp>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, _Up&& __x, _Vp&& __y)
-	{
-	  this->construct(__p, piecewise_construct,
-			  forward_as_tuple(std::forward<_Up>(__x)),
-			  forward_as_tuple(std::forward<_Vp>(__y)));
-	}
-
-      template <typename _Tp1, typename _Tp2, typename _Up, typename _Vp>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, const std::pair<_Up, _Vp>& __pr)
-	{
-	  this->construct(__p, piecewise_construct,
-			  forward_as_tuple(__pr.first),
-			  forward_as_tuple(__pr.second));
-	}
-
-      template<typename _Tp1, typename _Tp2, typename _Up, typename _Vp>
-	__attribute__((__nonnull__))
-	void
-	construct(pair<_Tp1, _Tp2>* __p, pair<_Up, _Vp>&& __pr)
-	{
-	  this->construct(__p, piecewise_construct,
-			  forward_as_tuple(std::forward<_Up>(__pr.first)),
-			  forward_as_tuple(std::forward<_Vp>(__pr.second)));
-	}
-#else
       template<typename _Tp1, typename... _Args>
 	__attribute__((__nonnull__))
 	void
 	construct(_Tp1* __p, _Args&&... __args)
 	{
-	  std::uninitialized_construct_using_allocator(__p, *this,
-	      std::forward<_Args>(__args)...);
+	  ::new ((void*)__p) _Tp1(std::make_from_tuple<_Tp1>(
+		std::__uses_alloc_args<_Tp1>(*this,
+		  std::forward<_Args>(__args)...)));
 	}
-#endif
 
       template<typename _Up>
 	__attribute__((__nonnull__))
@@ -270,30 +188,6 @@ namespace pmr
       { return _M_resource; }
 
     private:
-      using __uses_alloc1_ = __uses_alloc1<polymorphic_allocator>;
-      using __uses_alloc2_ = __uses_alloc2<polymorphic_allocator>;
-
-      template<typename _Ind, typename... _Args>
-	static tuple<_Args&&...>
-	_S_construct_p(__uses_alloc0, _Ind, tuple<_Args...>& __t)
-	{ return std::move(__t); }
-
-      template<size_t... _Ind, typename... _Args>
-	static tuple<allocator_arg_t, polymorphic_allocator, _Args&&...>
-	_S_construct_p(__uses_alloc1_ __ua, index_sequence<_Ind...>,
-		       tuple<_Args...>& __t)
-	{
-	  return {
-	      allocator_arg, *__ua._M_a, std::get<_Ind>(std::move(__t))...
-	  };
-	}
-
-      template<size_t... _Ind, typename... _Args>
-	static tuple<_Args&&..., polymorphic_allocator>
-	_S_construct_p(__uses_alloc2_ __ua, index_sequence<_Ind...>,
-		       tuple<_Args...>& __t)
-	{ return { std::get<_Ind>(std::move(__t))..., *__ua._M_a }; }
-
       memory_resource* _M_resource;
     };
diff mbox series

Patch

diff --git a/libstdc++-v3/include/std/memory b/libstdc++-v3/include/std/memory
index ff9add9cef2..00a85eef25e 100644
--- a/libstdc++-v3/include/std/memory
+++ b/libstdc++-v3/include/std/memory
@@ -91,6 +91,8 @@ 
 #include <cstdint>
 #if __cplusplus > 201703L
 # include <bit>			// for ispow2
+# include <new>			// for placement operator new
+# include <tuple>		// for tuple, make_tuple, make_from_tuple
 #endif
 namespace std _GLIBCXX_VISIBILITY(default)
 {
@@ -166,6 +168,197 @@  get_pointer_safety() noexcept { return pointer_safety::relaxed; }
     }
 #endif // C++2a
 
+#if __cplusplus > 201703L
+  template<typename _Tp>
+    struct __is_pair : false_type { };
+  template<typename _Tp, typename _Up>
+    struct __is_pair<pair<_Tp, _Up>> : true_type { };
+  template<typename _Tp, typename _Up>
+    struct __is_pair<const pair<_Tp, _Up>> : true_type { };
+
+  template<typename _Tp, typename __ = _Require<__not_<__is_pair<_Tp>>>,
+	   typename _Alloc, typename... _Args>
+    constexpr auto
+    __uses_alloc_args(const _Alloc& __a, _Args&&... __args) noexcept
+    {
+      if constexpr (uses_allocator_v<remove_cv_t<_Tp>, _Alloc>)
+	{
+	  if constexpr (is_constructible_v<_Tp, allocator_arg_t,
+					   const _Alloc&, _Args...>)
+	    {
+	      return tuple<allocator_arg_t, const _Alloc&, _Args&&...>(
+		  allocator_arg, __a, std::forward<_Args>(__args)...);
+	    }
+	  else
+	    {
+	      static_assert(is_constructible_v<_Tp, _Args..., const _Alloc&>);
+
+	      return tuple<_Args&&..., const _Alloc&>(
+		  std::forward<_Args>(__args)..., __a);
+	    }
+	}
+      else
+	{
+	  static_assert(is_constructible_v<_Tp, _Args...>);
+
+	  return tuple<_Args&&...>(std::forward<_Args>(__args)...);
+	}
+    }
+
+#if __cpp_concepts
+  template<typename _Tp>
+    concept bool _Std_pair = __is_pair<_Tp>::value;
+#endif
+
+// This is a temporary workaround until -fconcepts is implied by -std=gnu++2a
+#if __cpp_concepts
+# define _GLIBCXX_STD_PAIR_CONSTRAINT(T) _Std_pair T
+# define _GLIBCXX_STD_PAIR_CONSTRAINT_(T) _Std_pair T
+#else
+# define _GLIBCXX_STD_PAIR_CONSTRAINT(T) \
+      typename T, typename __ = _Require<__is_pair<T>>
+# define _GLIBCXX_STD_PAIR_CONSTRAINT_(T) typename T, typename
+#endif
+
+  template<typename _Tp,
+#if ! __cpp_concepts
+	   typename __ = _Require<__not_<__is_pair<_Tp>>>,
+#endif
+	   typename _Alloc, typename... _Args>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a,
+				     _Args&&... __args) noexcept
+#if __cpp_concepts
+    requires ! _Std_pair<_Tp>
+#endif
+    {
+      return std::__uses_alloc_args<_Tp>(__a, std::forward<_Args>(__args)...);
+    }
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
+	   typename _Tuple1, typename _Tuple2>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
+				     _Tuple1&& __x, _Tuple2&& __y) noexcept;
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc&) noexcept;
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
+	   typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc&, _Up&&, _Vp&&) noexcept;
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
+	   typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc&,
+				     const pair<_Up, _Vp>&) noexcept;
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT(_Tp), typename _Alloc,
+	   typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc&, pair<_Up, _Vp>&&) noexcept;
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
+	   typename _Tuple1, typename _Tuple2>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
+				     _Tuple1&& __x, _Tuple2&& __y) noexcept
+    {
+      using _Tp1 = typename _Tp::first_type;
+      using _Tp2 = typename _Tp::second_type;
+
+      return std::make_tuple(piecewise_construct,
+	  std::apply([&__a](auto&&... __args1) {
+	      return std::uses_allocator_construction_args<_Tp1>(
+		  __a, std::forward<decltype(__args1)>(__args1)...);
+	  }, std::forward<_Tuple1>(__x)),
+	  std::apply([&__a](auto&&... __args2) {
+	      return std::uses_allocator_construction_args<_Tp2>(
+		  __a, std::forward<decltype(__args2)>(__args2)...);
+	  }, std::forward<_Tuple2>(__y)));
+    }
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a) noexcept
+    {
+      using _Tp1 = typename _Tp::first_type;
+      using _Tp2 = typename _Tp::second_type;
+
+      return std::make_tuple(piecewise_construct,
+	  std::uses_allocator_construction_args<_Tp1>(__a),
+	  std::uses_allocator_construction_args<_Tp2>(__a));
+    }
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
+	   typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a, _Up&& __u, _Vp&& __v)
+      noexcept
+    {
+      using _Tp1 = typename _Tp::first_type;
+      using _Tp2 = typename _Tp::second_type;
+
+      return std::make_tuple(piecewise_construct,
+	  std::uses_allocator_construction_args<_Tp1>(__a,
+	    std::forward<_Up>(__u)),
+	  std::uses_allocator_construction_args<_Tp2>(__a,
+	    std::forward<_Vp>(__v)));
+    }
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
+	   typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a,
+				     const pair<_Up, _Vp>& __pr) noexcept
+    {
+      using _Tp1 = typename _Tp::first_type;
+      using _Tp2 = typename _Tp::second_type;
+
+      return std::make_tuple(piecewise_construct,
+	  std::uses_allocator_construction_args<_Tp1>(__a, __pr.first),
+	  std::uses_allocator_construction_args<_Tp2>(__a, __pr.second));
+    }
+
+  template<_GLIBCXX_STD_PAIR_CONSTRAINT_(_Tp), typename _Alloc,
+	   typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a,
+				     pair<_Up, _Vp>&& __pr) noexcept
+    {
+      using _Tp1 = typename _Tp::first_type;
+      using _Tp2 = typename _Tp::second_type;
+
+      return std::make_tuple(piecewise_construct,
+	  std::uses_allocator_construction_args<_Tp1>(__a,
+	    std::move(__pr).first),
+	  std::uses_allocator_construction_args<_Tp2>(__a,
+	    std::move(__pr).second));
+    }
+
+  template<typename _Tp, typename _Alloc, typename... _Args>
+    inline _Tp
+    make_obj_using_allocator(const _Alloc& __a, _Args&&... __args)
+    {
+      return std::make_from_tuple<_Tp>(uses_allocator_construction_args<_Tp>(
+	    __a, std::forward<_Args>(__args)...));
+    }
+
+  template<typename _Tp, typename _Alloc, typename... _Args>
+    inline _Tp*
+    uninitialized_construct_using_allocator(_Tp* __p, const _Alloc& __a,
+					    _Args&&... __args)
+    {
+      void* __vp = const_cast<void*>(static_cast<const volatile void*>(__p));
+      return ::new(__vp) _Tp(std::make_obj_using_allocator<_Tp>(__a,
+	    std::forward<_Args>(__args)...));
+    }
+
+#endif // C++2a
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
 #endif // C++11
diff --git a/libstdc++-v3/include/std/memory_resource b/libstdc++-v3/include/std/memory_resource
index 93b2ebd9759..a212bccc9b1 100644
--- a/libstdc++-v3/include/std/memory_resource
+++ b/libstdc++-v3/include/std/memory_resource
@@ -170,6 +170,7 @@  namespace pmr
       __attribute__((__nonnull__))
       { _M_resource->deallocate(__p, __n * sizeof(_Tp), alignof(_Tp)); }
 
+#if __cplusplus <= 201703L
       template<typename _Tp1, typename... _Args>
 	__attribute__((__nonnull__))
 	typename __not_pair<_Tp1>::type
@@ -242,6 +243,16 @@  namespace pmr
 			  forward_as_tuple(std::forward<_Up>(__pr.first)),
 			  forward_as_tuple(std::forward<_Vp>(__pr.second)));
 	}
+#else
+      template<typename _Tp1, typename... _Args>
+	__attribute__((__nonnull__))
+	void
+	construct(_Tp1* __p, _Args&&... __args)
+	{
+	  std::uninitialized_construct_using_allocator(__p, *this,
+	      std::forward<_Args>(__args)...);
+	}
+#endif
 
       template<typename _Up>
 	__attribute__((__nonnull__))
diff --git a/libstdc++-v3/testsuite/20_util/polymorphic_allocator/construct_c++2a.cc b/libstdc++-v3/testsuite/20_util/polymorphic_allocator/construct_c++2a.cc
new file mode 100644
index 00000000000..9048ca196ff
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/polymorphic_allocator/construct_c++2a.cc
@@ -0,0 +1,125 @@ 
+// Copyright (C) 2016-2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do run { target c++2a } }
+
+#include <memory_resource>
+#include <utility>
+#include <tuple>
+
+struct do_not_copy {
+  do_not_copy() = default;
+  do_not_copy(const do_not_copy&) { throw 1; }
+};
+
+void
+test01()
+{
+  struct X {
+    X(do_not_copy&&) { }
+  };
+
+  using pair = std::pair<X, int>;
+  std::pmr::polymorphic_allocator<pair> a;
+  auto ptr = a.allocate(1);
+  a.construct(ptr, std::piecewise_construct,
+      std::tuple<do_not_copy>{}, std::make_tuple(1));
+  a.deallocate(ptr, 1);
+}
+
+void
+test02()
+{
+  struct X {
+    using allocator_type = std::pmr::polymorphic_allocator<int>;
+    X(do_not_copy&&, const allocator_type&) { }
+  };
+
+  using pair = std::pair<X, int>;
+  std::pmr::polymorphic_allocator<pair> a;
+  auto ptr = a.allocate(1);
+  a.construct(ptr, std::piecewise_construct,
+      std::tuple<do_not_copy>{}, std::make_tuple(1));
+  a.deallocate(ptr, 1);
+}
+
+void
+test03()
+{
+  struct X {
+    using allocator_type = std::pmr::polymorphic_allocator<int>;
+    X(std::allocator_arg_t, const allocator_type&, do_not_copy&&) { }
+  };
+
+  using pair = std::pair<X, int>;
+  std::pmr::polymorphic_allocator<pair> a;
+  auto ptr = a.allocate(1);
+  a.construct(ptr, std::piecewise_construct,
+      std::tuple<do_not_copy>{}, std::make_tuple(1));
+  a.deallocate(ptr, 1);
+}
+
+void
+test04()
+{
+  struct X
+  {
+    using allocator_type = std::pmr::polymorphic_allocator<int>;
+    X() = default;
+    X(const X&) { throw 1; }
+    X(const X&, const allocator_type&) { }
+  };
+
+  struct Y
+  {
+    using allocator_type = std::pmr::polymorphic_allocator<int>;
+    Y() = default;
+    Y(const Y&) = delete;
+    Y(std::allocator_arg_t, const allocator_type&, const Y&) { }
+  };
+
+  using pair_type = std::pair<X, Y>;
+  std::pmr::polymorphic_allocator<pair_type> a;
+  auto ptr = a.allocate(1);
+  /* not const */ pair_type p;
+  a.construct(ptr, p); // LWG 2975
+  a.deallocate(ptr, 1);
+}
+
+void
+test05()
+{
+  struct X {
+    using allocator_type = std::pmr::polymorphic_allocator<char>;
+    X(int);
+    X(int, const allocator_type&) { }
+  };
+  std::pmr::polymorphic_allocator<X> a;
+  auto ptr = a.allocate(1);
+  a.construct(ptr, 1);
+  a.deallocate(ptr, 1);
+}
+
+int main()
+{
+  test01();
+  test02();
+  test03();
+  test04();
+  test05();
+}
diff --git a/libstdc++-v3/testsuite/20_util/uses_allocator/make_obj.cc b/libstdc++-v3/testsuite/20_util/uses_allocator/make_obj.cc
new file mode 100644
index 00000000000..670c59afb33
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/uses_allocator/make_obj.cc
@@ -0,0 +1,403 @@ 
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do run { target c++2a } }
+
+#include <testsuite_hooks.h>
+#include <testsuite_allocator.h>
+
+using test_allocator = __gnu_test::uneq_allocator<int>;
+
+struct Arg { };
+
+struct A
+{
+  A() : nargs(0) { }
+  A(float&) : nargs(1) { }
+  A(int, void*) : nargs(2) { }
+
+  // These should not be used:
+  A(const test_allocator& a);
+  A(float&, const test_allocator& a);
+  A(int, void*, const test_allocator& a);
+
+  const int nargs;
+  const int alloc_id = -1;
+
+  // std::uses_allocator<A, test_allocator> should be false:
+  using allocator_type = void*();
+};
+
+struct B
+{
+  // This means std::uses_allocator<B, test_allocator> is true:
+  using allocator_type = test_allocator;
+
+  B() : nargs(0) { }
+  B(float&) : nargs(1) { }
+  B(int, void*) : nargs(2) { }
+
+  B(std::allocator_arg_t, const test_allocator& a)
+  : nargs(0), alloc_id(a.get_personality()) { }
+  B(std::allocator_arg_t, const test_allocator& a, float&)
+  : nargs(1), alloc_id(a.get_personality()) { }
+  B(std::allocator_arg_t, const test_allocator& a, int, void*)
+  : nargs(2), alloc_id(a.get_personality()) { }
+  B(std::allocator_arg_t, const test_allocator& a, B&& b)
+  : nargs(b.nargs), alloc_id(a.get_personality()) { }
+
+  // These should not be used:
+  B(const test_allocator&);
+  B(float&, const test_allocator&, float&);
+  B(int, void*, const test_allocator&);
+  B(const test_allocator&, float&);
+  B(const test_allocator&, int, void*);
+  B(B&&);
+  B(B&&, const test_allocator&);
+
+  const int nargs;
+  const int alloc_id = -1;
+};
+
+struct C
+{
+  C() : nargs(0) { }
+  C(float&) : nargs(1) { }
+  C(int, void*) : nargs(2) { }
+
+  C(const test_allocator& a)
+  : nargs(0), alloc_id(a.get_personality()) { }
+  C(float&, const test_allocator& a)
+  : nargs(1), alloc_id(a.get_personality()) { }
+  C(int, void*, const test_allocator& a)
+  : nargs(2), alloc_id(a.get_personality()) { }
+  C(C&& c, const test_allocator& a)
+  : nargs(c.nargs), alloc_id(a.get_personality()) { }
+
+  C(C&&);
+
+  const int nargs;
+  const int alloc_id = -1;
+};
+
+namespace std {
+  // This means std::uses_allocator<C, test_allocator> is true:
+  template<> struct uses_allocator<C, test_allocator> : std::true_type { };
+}
+
+test_allocator alloc1(1);
+test_allocator alloc2(2);
+
+void
+test01()
+{
+  auto i0 = std::make_obj_using_allocator<int>(alloc1, 2);
+  VERIFY( i0 == 2 );
+
+  float f = 0.0f;
+
+  auto a0 = std::make_obj_using_allocator<A>(alloc1);
+  VERIFY( a0.nargs == 0 );
+  VERIFY( a0.alloc_id == -1 );
+  auto a1 = std::make_obj_using_allocator<A>(alloc1, f);
+  VERIFY( a1.nargs == 1 );
+  VERIFY( a1.alloc_id == -1 );
+  auto a2 = std::make_obj_using_allocator<A>(alloc1, 123, nullptr);
+  VERIFY( a2.nargs == 2 );
+  VERIFY( a2.alloc_id == -1 );
+
+  auto b0 = std::make_obj_using_allocator<B>(alloc1);
+  VERIFY( b0.nargs == 0 );
+  VERIFY( b0.alloc_id == 1 );
+  auto b1 = std::make_obj_using_allocator<B>(alloc2, f);
+  VERIFY( b1.nargs == 1 );
+  VERIFY( b1.alloc_id == 2 );
+  auto b2 = std::make_obj_using_allocator<B>(alloc1, 123, nullptr);
+  VERIFY( b2.nargs == 2 );
+  VERIFY( b2.alloc_id == 1 );
+
+  auto c0 = std::make_obj_using_allocator<C>(alloc1);
+  VERIFY( c0.nargs == 0 );
+  VERIFY( c0.alloc_id == 1 );
+  auto c1 = std::make_obj_using_allocator<C>(alloc2, f);
+  VERIFY( c1.nargs == 1 );
+  VERIFY( c1.alloc_id == 2 );
+  auto c2 = std::make_obj_using_allocator<C>(alloc1, 123, nullptr);
+  VERIFY( c2.nargs == 2 );
+  VERIFY( c2.alloc_id == 1 );
+}
+
+void 
+test02()
+{
+  decltype(auto) b
+    = std::make_obj_using_allocator<const B>(alloc1, 123, nullptr);
+  static_assert( std::is_const_v<decltype(b)> );
+  VERIFY( b.nargs == 2 );
+  VERIFY( b.alloc_id == 1 );
+
+  decltype(auto) c = std::make_obj_using_allocator<const C>(alloc1);
+  static_assert( std::is_const_v<decltype(c)> );
+  VERIFY( c.nargs == 0 );
+  VERIFY( c.alloc_id == 1 );
+}
+
+void
+test03()
+{
+  B b;
+  decltype(auto) ref = std::make_obj_using_allocator<B&>(alloc1, b);
+  static_assert( std::is_same_v<decltype(ref), B&> );
+  VERIFY( &ref == &b );
+  VERIFY( ref.nargs == 0 );
+  VERIFY( ref.alloc_id == -1 );
+  const B& cref = std::make_obj_using_allocator<const B&>(alloc1, b);
+  static_assert( std::is_same_v<decltype(cref), const B&> );
+  VERIFY( &cref == &b );
+  VERIFY( cref.nargs == 0 );
+  VERIFY( cref.alloc_id == -1 );
+}
+
+void
+test04()
+{
+  struct D
+  {
+    D(std::allocator_arg_t) { }
+    D(std::allocator_arg_t, int) { }
+
+    // These should not be used:
+    D(std::allocator_arg_t, const test_allocator&);
+    D(std::allocator_arg_t, const test_allocator&, int);
+
+    ~D() { }
+  };
+
+  D d1 = std::make_obj_using_allocator<D>(alloc1, std::allocator_arg);
+
+  struct E
+  {
+    using allocator_type = test_allocator;
+
+    E(std::allocator_arg_t, const test_allocator&) { }
+    E(std::allocator_arg_t, int, const test_allocator&) { }
+
+    // These should not be used:
+    E(std::allocator_arg_t);
+    E(std::allocator_arg_t, int);
+
+    ~E() { }
+  };
+
+  E e1 = std::make_obj_using_allocator<E>(alloc1, std::allocator_arg);
+  E e2 = std::make_obj_using_allocator<E>(alloc2, std::allocator_arg, 2);
+}
+
+void
+test05()
+{
+  using std::pair;
+  std::piecewise_construct_t p;
+  std::tuple<> t0;
+  float f = 0.0f;
+  std::tuple<float&> t1(f);
+  std::tuple<int, void*> t2{};
+
+  auto aa00 = std::make_obj_using_allocator<pair<A, A>>(alloc1, p, t0, t0);
+  VERIFY( aa00.first.nargs == 0 );
+  VERIFY( aa00.first.alloc_id == -1 );
+  VERIFY( aa00.second.nargs == 0 );
+  VERIFY( aa00.second.alloc_id == -1 );
+  auto ab00 = std::make_obj_using_allocator<pair<A, B>>(alloc1, p, t0, t0);
+  VERIFY( ab00.first.nargs == 0 );
+  VERIFY( ab00.first.alloc_id == -1 );
+  VERIFY( ab00.second.nargs == 0 );
+  VERIFY( ab00.second.alloc_id == 1 );
+  auto bc00 = std::make_obj_using_allocator<pair<B, C>>(alloc2, p, t0, t0);
+  VERIFY( bc00.first.nargs == 0 );
+  VERIFY( bc00.first.alloc_id == 2 );
+  VERIFY( bc00.second.nargs == 0 );
+  VERIFY( bc00.second.alloc_id == 2 );
+  auto cb00 = std::make_obj_using_allocator<pair<C, B>>(alloc2, p, t0, t0);
+  VERIFY( cb00.first.nargs == 0 );
+  VERIFY( cb00.first.alloc_id == 2 );
+  VERIFY( cb00.second.nargs == 0 );
+  VERIFY( cb00.second.alloc_id == 2 );
+  auto cc00
+    = std::make_obj_using_allocator<pair<C, const C>>(alloc1, p, t0, t0);
+  VERIFY( cc00.first.nargs == 0 );
+  VERIFY( cc00.first.alloc_id == 1 );
+  VERIFY( cc00.second.nargs == 0 );
+  VERIFY( cc00.second.alloc_id == 1 );
+
+  auto aa21 = std::make_obj_using_allocator<pair<A, A>>(alloc1, p, t2, t1);
+  VERIFY( aa21.first.nargs == 2 );
+  VERIFY( aa21.first.alloc_id == -1 );
+  VERIFY( aa21.second.nargs == 1 );
+  VERIFY( aa21.second.alloc_id == -1 );
+  auto ab21 = std::make_obj_using_allocator<pair<A, B>>(alloc1, p, t2, t1);
+  VERIFY( ab21.first.nargs == 2 );
+  VERIFY( ab21.first.alloc_id == -1 );
+  VERIFY( ab21.second.nargs == 1 );
+  VERIFY( ab21.second.alloc_id == 1 );
+  auto bc11 = std::make_obj_using_allocator<pair<B, C>>(alloc2, p, t1, t1);
+  VERIFY( bc11.first.nargs == 1 );
+  VERIFY( bc11.first.alloc_id == 2 );
+  VERIFY( bc11.second.nargs == 1 );
+  VERIFY( bc11.second.alloc_id == 2 );
+  auto cb12 = std::make_obj_using_allocator<pair<C, B>>(alloc2, p, t1, t2);
+  VERIFY( cb12.first.nargs == 1 );
+  VERIFY( cb12.first.alloc_id == 2 );
+  VERIFY( cb12.second.nargs == 2 );
+  VERIFY( cb12.second.alloc_id == 2 );
+  auto cc22
+    = std::make_obj_using_allocator<pair<C, const C>>(alloc1, p, t2, t1);
+  VERIFY( cc22.first.nargs == 2 );
+  VERIFY( cc22.first.alloc_id == 1 );
+  VERIFY( cc22.second.nargs == 1 );
+  VERIFY( cc22.second.alloc_id == 1 );
+}
+
+void
+test06()
+{
+  using std::pair;
+  float f = 0.0f;
+
+  auto aa00 = std::make_obj_using_allocator<pair<A, A>>(alloc1);
+  VERIFY( aa00.first.nargs == 0 );
+  VERIFY( aa00.first.alloc_id == -1 );
+  VERIFY( aa00.second.nargs == 0 );
+  VERIFY( aa00.second.alloc_id == -1 );
+  auto ab00 = std::make_obj_using_allocator<pair<A, B>>(alloc1);
+  VERIFY( ab00.first.nargs == 0 );
+  VERIFY( ab00.first.alloc_id == -1 );
+  VERIFY( ab00.second.nargs == 0 );
+  VERIFY( ab00.second.alloc_id == 1 );
+  auto bc00 = std::make_obj_using_allocator<pair<B, C>>(alloc2);
+  VERIFY( bc00.first.nargs == 0 );
+  VERIFY( bc00.first.alloc_id == 2 );
+  VERIFY( bc00.second.nargs == 0 );
+  VERIFY( bc00.second.alloc_id == 2 );
+  auto cb00 = std::make_obj_using_allocator<pair<C, B>>(alloc2);
+  VERIFY( cb00.first.nargs == 0 );
+  VERIFY( cb00.first.alloc_id == 2 );
+  VERIFY( cb00.second.nargs == 0 );
+  VERIFY( cb00.second.alloc_id == 2 );
+  auto cc00 = std::make_obj_using_allocator<pair<C, const C>>(alloc1);
+  VERIFY( cc00.first.nargs == 0 );
+  VERIFY( cc00.first.alloc_id == 1 );
+  VERIFY( cc00.second.nargs == 0 );
+  VERIFY( cc00.second.alloc_id == 1 );
+
+  auto aa11 = std::make_obj_using_allocator<pair<A, A>>(alloc1, f, f);
+  VERIFY( aa11.first.nargs == 1 );
+  VERIFY( aa11.first.alloc_id == -1 );
+  VERIFY( aa11.second.nargs == 1 );
+  VERIFY( aa11.second.alloc_id == -1 );
+  auto aba1 = std::make_obj_using_allocator<pair<A, B>>(alloc1, A{}, f);
+  VERIFY( aba1.first.nargs == 0 );
+  VERIFY( aba1.first.alloc_id == -1 );
+  VERIFY( aba1.second.nargs == 1 );
+  VERIFY( aba1.second.alloc_id == 1 );
+  auto bc11 = std::make_obj_using_allocator<pair<B, C>>(alloc2, f, f);
+  VERIFY( bc11.first.nargs == 1 );
+  VERIFY( bc11.first.alloc_id == 2 );
+  VERIFY( bc11.second.nargs == 1 );
+  VERIFY( bc11.second.alloc_id == 2 );
+  auto cb1b = std::make_obj_using_allocator<pair<C, B>>(alloc2, f, B{});
+  VERIFY( cb1b.first.nargs == 1 );
+  VERIFY( cb1b.first.alloc_id == 2 );
+  VERIFY( cb1b.second.nargs == 0 );
+  VERIFY( cb1b.second.alloc_id == 2 );
+  auto cccc
+    = std::make_obj_using_allocator<pair<C, const C>>(alloc1, C{}, C{});
+  VERIFY( cccc.first.nargs == 0 );
+  VERIFY( cccc.first.alloc_id == 1 );
+  VERIFY( cccc.second.nargs == 0 );
+  VERIFY( cccc.second.alloc_id == 1 );
+
+  pair<float&, A> p1a(f, A{});
+  pair<float&, float&> p11(f, f);
+  auto aa1a = std::make_obj_using_allocator<pair<A, A>>(alloc1, p1a);
+  VERIFY( aa1a.first.nargs == 1 );
+  VERIFY( aa1a.first.alloc_id == -1 );
+  VERIFY( aa1a.second.nargs == 0 );
+  VERIFY( aa1a.second.alloc_id == -1 );
+  auto ab11 = std::make_obj_using_allocator<pair<A, B>>(alloc1, p11);
+  VERIFY( ab11.first.nargs == 1 );
+  VERIFY( ab11.first.alloc_id == -1 );
+  VERIFY( ab11.second.nargs == 1 );
+  VERIFY( ab11.second.alloc_id == 1 );
+  auto cb11 = std::make_obj_using_allocator<pair<C, B>>(alloc2, p11);
+  VERIFY( cb11.first.nargs == 1 );
+  VERIFY( cb11.first.alloc_id == 2 );
+  VERIFY( cb11.second.nargs == 1 );
+  VERIFY( cb11.second.alloc_id == 2 );
+
+  auto bcbc = std::make_obj_using_allocator<pair<B, C>>(alloc2, pair<B, C>());
+  VERIFY( bcbc.first.nargs == 0 );
+  VERIFY( bcbc.first.alloc_id == 2 );
+  VERIFY( bcbc.second.nargs == 0 );
+  VERIFY( bcbc.second.alloc_id == 2 );
+
+  auto cc11 = std::make_obj_using_allocator<pair<C, B>>(alloc2, std::move(p11));
+  VERIFY( cc11.first.nargs == 1 );
+  VERIFY( cc11.first.alloc_id == 2 );
+  VERIFY( cc11.second.nargs == 1 );
+  VERIFY( cc11.second.alloc_id == 2 );
+}
+
+void
+test07()
+{
+  using nested_pair = std::pair<const std::pair<B, const B>, C>;
+  auto p = std::make_obj_using_allocator<const nested_pair>(alloc1);
+  VERIFY( p.first.first.alloc_id == 1 );
+  VERIFY( p.first.second.alloc_id == 1 );
+  VERIFY( p.second.alloc_id == 1 );
+}
+
+void
+test08()
+{
+  // LWG DR 3187.
+  // P0591R4 reverted DR 2586 fixes to scoped_allocator_adaptor::construct()
+
+  struct X {
+    using allocator_type = std::allocator<X>;
+    X(std::allocator_arg_t, allocator_type&&) { }
+    X(const allocator_type&) { }
+  };
+
+  std::allocator<X> a;
+  std::make_obj_using_allocator<X>(a);
+}
+
+int
+main()
+{
+  test01();
+  test02();
+  test03();
+  test04();
+  test05();
+  test06();
+  test07();
+  test08();
+}