This is the mail archive of the libstdc++@gcc.gnu.org mailing list for the libstdc++ project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH] C++2a Utility functions to implement uses-allocator construction (P0591R4)


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;
     };
 

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]