Currently the __relocate_a function implementing in-place relocation for
std::vector only optimizes to memcpy for std::allocator, which is safe
because we know the precise effects of std::allocator::construct and
std::allocator::destroy. We could extend the optimization to other
allocators which do not have construct and detroy members, because those
will use the default implementations in std::allocator_traits, and we
know the effects of those members.

As well as enabling memcpy for more allocators, this also changes
__relocate_a to use 'if constexpr' instead of dispatching to a pair of
overloaded function templates, and to enable the memcpy optimization for
arbitrary contiguous iterators, not only for pointers.

libstdc++-v3/ChangeLog:

        PR libstdc++/87604
        * include/bits/stl_uninitialized.h (__relocate_a_1): Remove.
        (__relocate_a): Extend memcpy optimization to contiguous
        iterators and to allocators that don't have custom
        construct and destroy members.
---

As Marc pointed out in bugzilla, we are too conservative about using
trivial relocation, only enabling it for std::allocator. This extends it
to non-standard allocators that don't customize construct and destroy.

N.B. this doesn't benefit pmr::polymorphic_allocator because that has a
custom construct member, so that it can do uses-allocator construction.
We could potentially make it work, because we know that if the type
doesn't support uses-allocator construction then polymorphic_allocator
will construct objects the same way as std::allocator would. We would
need to check whether std::uses_allocator is true and the type has an
allcoator-extended move constructor (in either the leading or trailing
allocator form). Making that work is left for another day.

Tested x86_64-linux.

 libstdc++-v3/include/bits/stl_uninitialized.h | 125 ++++++++++--------
 1 file changed, 69 insertions(+), 56 deletions(-)

diff --git a/libstdc++-v3/include/bits/stl_uninitialized.h 
b/libstdc++-v3/include/bits/stl_uninitialized.h
index b7e65eb3ca0..be18289463c 100644
--- a/libstdc++-v3/include/bits/stl_uninitialized.h
+++ b/libstdc++-v3/include/bits/stl_uninitialized.h
@@ -1251,70 +1251,83 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     : __bool_constant<__is_trivial(_Tp)>
     { };
 
-  template <typename _InputIterator, typename _ForwardIterator,
-           typename _Allocator>
-    _GLIBCXX20_CONSTEXPR
-    inline _ForwardIterator
-    __relocate_a_1(_InputIterator __first, _InputIterator __last,
-                  _ForwardIterator __result, _Allocator& __alloc)
-    noexcept(noexcept(std::__relocate_object_a(std::addressof(*__result),
-                                              std::addressof(*__first),
-                                              __alloc)))
-    {
-      typedef typename iterator_traits<_InputIterator>::value_type
-       _ValueType;
-      typedef typename iterator_traits<_ForwardIterator>::value_type
-       _ValueType2;
-      static_assert(std::is_same<_ValueType, _ValueType2>::value,
-         "relocation is only possible for values of the same type");
-      _ForwardIterator __cur = __result;
-      for (; __first != __last; ++__first, (void)++__cur)
-       std::__relocate_object_a(std::__addressof(*__cur),
-                                std::__addressof(*__first), __alloc);
-      return __cur;
-    }
-
-#if _GLIBCXX_HOSTED
-  template <typename _Tp, typename _Up>
-    _GLIBCXX20_CONSTEXPR
-    inline __enable_if_t<std::__is_bitwise_relocatable<_Tp>::value, _Tp*>
-    __relocate_a_1(_Tp* __first, _Tp* __last,
-                  _Tp* __result,
-                  [[__maybe_unused__]] allocator<_Up>& __alloc) noexcept
-    {
-      ptrdiff_t __count = __last - __first;
-      if (__count > 0)
-       {
-#ifdef __cpp_lib_is_constant_evaluated
-         if (std::is_constant_evaluated())
-           {
-             // Can't use memcpy. Wrap the pointer so that __relocate_a_1
-             // resolves to the non-trivial overload above.
-             __gnu_cxx::__normal_iterator<_Tp*, void> __out(__result);
-             __out = std::__relocate_a_1(__first, __last, __out, __alloc);
-             return __out.base();
-           }
-#endif
-         __builtin_memcpy(__result, __first, __count * sizeof(_Tp));
-       }
-      return __result + __count;
-    }
-#endif
-
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
   template <typename _InputIterator, typename _ForwardIterator,
            typename _Allocator>
     _GLIBCXX20_CONSTEXPR
     inline _ForwardIterator
     __relocate_a(_InputIterator __first, _InputIterator __last,
                 _ForwardIterator __result, _Allocator& __alloc)
-    noexcept(noexcept(__relocate_a_1(std::__niter_base(__first),
-                                    std::__niter_base(__last),
-                                    std::__niter_base(__result), __alloc)))
+    noexcept(noexcept(__relocate_object_a(std::__to_address(__first),
+                                         std::__to_address(__result),
+                                         __alloc)))
     {
-      return std::__relocate_a_1(std::__niter_base(__first),
-                                std::__niter_base(__last),
-                                std::__niter_base(__result), __alloc);
+      using _Dest = decltype(std::__niter_base(__result));
+      using _Src = decltype(std::__niter_base(__first));
+      using _ValT = typename iterator_traits<_ForwardIterator>::value_type;
+      using _ValT2 = typename iterator_traits<_InputIterator>::value_type;
+      static_assert(is_same<_ValT, _ValT2>::value,
+         "relocation is only possible for values of the same type");
+
+      if constexpr (__is_bitwise_relocatable<_ValT>::value)
+       {
+         struct _ATraits : __allocator_traits_base
+         {
+           using __allocator_traits_base::__has_construct;
+           using __allocator_traits_base::__has_destroy;
+         };
+#if _GLIBCXX_HOSTED
+         constexpr bool __is_std_allocator
+           = is_same<_Allocator, allocator<_ValT>>::value;
+#else
+         constexpr bool __is_std_allocator = false;
+#endif
+         constexpr bool __has_custom_cons_dest
+           = _ATraits::template __has_construct<_Allocator, _ValT>
+               && _ATraits::template __has_destroy<_Allocator, _ValT>;
+
+         if constexpr (!__is_std_allocator && __has_custom_cons_dest)
+           ; // fall through to the loop below
+         else if (!std::__is_constant_evaluated())
+           {
+             if constexpr (__and_<is_pointer<_Dest>, is_pointer<_Src>>::value)
+               {
+                 ptrdiff_t __n = __last - __first;
+                 if (__n > 0) [[__likely__]]
+                   {
+                     __builtin_memcpy(std::__niter_base(__result),
+                                      std::__niter_base(__first),
+                                      __n * sizeof(_ValT));
+                     __result += __n;
+                   }
+                 return __result;
+               }
+#if __cpp_lib_concepts
+             else if constexpr (contiguous_iterator<_Dest>
+                                  && contiguous_iterator<_Src>)
+               {
+                 if (auto __n = __last - __first; __n > 0) [[likely]]
+                   {
+                     void* __dest = std::to_address(__result);
+                     const void* __src = std::to_address(__first);
+                     size_t __nbytes = __n * sizeof(_ValT);
+                     __builtin_memcpy(__dest, __src, __n * sizeof(_ValT));
+                     __result += __n;
+                   }
+                 return __result;
+               }
+#endif
+           }
+       }
+
+      _ForwardIterator __cur = __result;
+      for (; __first != __last; ++__first, (void)++__cur)
+       std::__relocate_object_a(std::__addressof(*__cur),
+                                std::__addressof(*__first), __alloc);
+      return __cur;
     }
+#pragma GCC diagnostic pop
 
   /// @endcond
 #endif // C++11
-- 
2.47.1

Reply via email to