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