On Thu, 17 Oct 2024, 03:04 Patrick Palka, <[email protected]> wrote:
> On Tue, 15 Oct 2024, Jonathan Wakely wrote:
>
> > This is a slightly different approach to C++98 compatibility than used
> > in patch 1/1 of this series for the uninitialized algos. It worked out a
> > bit cleaner this way for these algos, I think.
> >
> > Tested x86_64-linux.
> >
> > -- >8 --
> >
> > This removes all the __copy_move class template specializations that
> > decide how to optimize std::copy and std::copy_n. We can inline those
> > optimizations into the algorithms, using if-constexpr (and macros for
> > C++98 compatibility) and remove the code dispatching to the various
> > class template specializations.
> >
> > Doing this means we implement the optimization directly for std::copy_n
> > instead of deferring to std::copy, That avoids the unwanted consequence
> > of advancing the iterator in copy_n only to take the difference later to
> > get back to the length that we already had in copy_n originally (as
> > described in PR 115444).
> >
> > With the new flattened implementations, we can also lower contiguous
> > iterators to pointers in std::copy/std::copy_n/std::copy_backwards, so
> > that they benefit from the same memmove optimizations as pointers.
> > There's a subtlety though: contiguous iterators can potentially throw
> > exceptions to exit the algorithm early. So we can only transform the
> > loop to memmove if dereferencing the iterator is noexcept. We don't
> > check that incrementing the iterator is noexcept because we advance the
> > contiguous iterators before using memmove, so that if incrementing would
> > throw, that happens first. I am writing a proposal (P3249R0) which would
> > make this unnecessary, so I hope we can drop the nothrow requirements
> > later.
> >
> > This change also solves PR 114817 by checking is_trivially_assignable
> > before optimizing copy/copy_n etc. to memmove. It's not enough to check
> > that the types are trivially copyable (a precondition for using memmove
> > at all), we also need to check that the specific assignment that would
> > be performed by the algorithm is also trivial. Replacing a non-trivial
> > assignment with memmove would be observable, so not allowed.
> >
> > libstdc++-v3/ChangeLog:
> >
> > PR libstdc++/115444
> > PR libstdc++/114817
> > * include/bits/stl_algo.h (__copy_n): Remove generic overload
> > and overload for random access iterators.
> > (copy_n): Inline generic version of __copy_n here. Do not defer
> > to std::copy for random access iterators.
> > * include/bits/stl_algobase.h (__copy_move): Remove.
> > (__nothrow_contiguous_iterator, __memcpyable_iterators): New
> > concepts.
> > (__assign_one, _GLIBCXX_TO_ADDR, _GLIBCXX_ADVANCE): New helpers.
> > (__copy_move_a2): Inline __copy_move logic and conditional
> > memmove optimization into the most generic overload.
> > (__copy_n_a): Likewise.
> > (__copy_move_backward): Remove.
> > (__copy_move_backward_a2): Inline __copy_move_backward logic and
> > memmove optimization into the most generic overload.
> > *
> testsuite/20_util/specialized_algorithms/uninitialized_copy/114817.cc:
> > New test.
> > *
> testsuite/20_util/specialized_algorithms/uninitialized_copy_n/114817.cc:
> > New test.
> > * testsuite/25_algorithms/copy/114817.cc: New test.
> > * testsuite/25_algorithms/copy/115444.cc: New test.
> > * testsuite/25_algorithms/copy_n/114817.cc: New test.
> > ---
> > libstdc++-v3/include/bits/stl_algo.h | 24 +-
> > libstdc++-v3/include/bits/stl_algobase.h | 426 +++++++++---------
> > .../uninitialized_copy/114817.cc | 39 ++
> > .../uninitialized_copy_n/114817.cc | 39 ++
> > .../testsuite/25_algorithms/copy/114817.cc | 38 ++
> > .../testsuite/25_algorithms/copy/115444.cc | 93 ++++
> > .../testsuite/25_algorithms/copy_n/114817.cc | 38 ++
> > 7 files changed, 469 insertions(+), 228 deletions(-)
> > create mode 100644
> libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/114817.cc
> > create mode 100644
> libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/114817.cc
> > create mode 100644 libstdc++-v3/testsuite/25_algorithms/copy/114817.cc
> > create mode 100644 libstdc++-v3/testsuite/25_algorithms/copy/115444.cc
> > create mode 100644 libstdc++-v3/testsuite/25_algorithms/copy_n/114817.cc
> >
> > diff --git a/libstdc++-v3/include/bits/stl_algo.h
> b/libstdc++-v3/include/bits/stl_algo.h
> > index a1ef665506d..489ce7e14d2 100644
> > --- a/libstdc++-v3/include/bits/stl_algo.h
> > +++ b/libstdc++-v3/include/bits/stl_algo.h
> > @@ -665,25 +665,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > return __result;
> > }
> >
> > - template<typename _InputIterator, typename _Size, typename
> _OutputIterator>
> > - _GLIBCXX20_CONSTEXPR
> > - _OutputIterator
> > - __copy_n(_InputIterator __first, _Size __n,
> > - _OutputIterator __result, input_iterator_tag)
> > - {
> > - return std::__niter_wrap(__result,
> > - __copy_n_a(__first, __n,
> > - std::__niter_base(__result),
> true));
> > - }
> > -
> > - template<typename _RandomAccessIterator, typename _Size,
> > - typename _OutputIterator>
> > - _GLIBCXX20_CONSTEXPR
> > - inline _OutputIterator
> > - __copy_n(_RandomAccessIterator __first, _Size __n,
> > - _OutputIterator __result, random_access_iterator_tag)
> > - { return std::copy(__first, __first + __n, __result); }
> > -
> > /**
> > * @brief Copies the range [first,first+n) into [result,result+n).
> > * @ingroup mutating_algorithms
> > @@ -714,8 +695,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > __glibcxx_requires_can_increment(__first, __n2);
> > __glibcxx_requires_can_increment(__result, __n2);
> >
> > - return std::__copy_n(__first, __n2, __result,
> > - std::__iterator_category(__first));
> > + auto __res = std::__copy_n_a(std::__niter_base(__first), __n2,
> > + std::__niter_base(__result), true);
> > + return std::__niter_wrap(__result, std::move(__res));
> > }
> >
> > /**
> > diff --git a/libstdc++-v3/include/bits/stl_algobase.h
> b/libstdc++-v3/include/bits/stl_algobase.h
> > index 751b7ad119b..5f77b00be9b 100644
> > --- a/libstdc++-v3/include/bits/stl_algobase.h
> > +++ b/libstdc++-v3/include/bits/stl_algobase.h
> > @@ -77,6 +77,7 @@
> > #endif
> > #if __cplusplus >= 202002L
> > # include <compare>
> > +# include <bits/ptr_traits.h> // std::to_address
> > #endif
> >
> > namespace std _GLIBCXX_VISIBILITY(default)
> > @@ -308,110 +309,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > return __a;
> > }
> >
> > - // All of these auxiliary structs serve two purposes. (1) Replace
> > - // calls to copy with memmove whenever possible. (Memmove, not
> memcpy,
> > - // because the input and output ranges are permitted to overlap.)
> > - // (2) If we're using random access iterators, then write the loop as
> > - // a for loop with an explicit count.
> > -
> > - template<bool _IsMove, bool _IsSimple, typename _Category>
> > - struct __copy_move
> > - {
> > - template<typename _II, typename _OI>
> > - _GLIBCXX20_CONSTEXPR
> > - static _OI
> > - __copy_m(_II __first, _II __last, _OI __result)
> > - {
> > - for (; __first != __last; ++__result, (void)++__first)
> > - *__result = *__first;
> > - return __result;
> > - }
> > - };
> > -
> > -#if __cplusplus >= 201103L
> > - template<typename _Category>
> > - struct __copy_move<true, false, _Category>
> > - {
> > - template<typename _II, typename _OI>
> > - _GLIBCXX20_CONSTEXPR
> > - static _OI
> > - __copy_m(_II __first, _II __last, _OI __result)
> > - {
> > - for (; __first != __last; ++__result, (void)++__first)
> > - *__result = std::move(*__first);
> > - return __result;
> > - }
> > - };
> > -#endif
> > -
> > - template<>
> > - struct __copy_move<false, false, random_access_iterator_tag>
> > - {
> > - template<typename _II, typename _OI>
> > - _GLIBCXX20_CONSTEXPR
> > - static _OI
> > - __copy_m(_II __first, _II __last, _OI __result)
> > - {
> > - typedef typename iterator_traits<_II>::difference_type _Distance;
> > - for(_Distance __n = __last - __first; __n > 0; --__n)
> > - {
> > - *__result = *__first;
> > - ++__first;
> > - ++__result;
> > - }
> > - return __result;
> > - }
> > -
> > - template<typename _Tp, typename _Up>
> > - static void
> > - __assign_one(_Tp* __to, _Up* __from)
> > - { *__to = *__from; }
> > - };
> > -
> > -#if __cplusplus >= 201103L
> > - template<>
> > - struct __copy_move<true, false, random_access_iterator_tag>
> > - {
> > - template<typename _II, typename _OI>
> > - _GLIBCXX20_CONSTEXPR
> > - static _OI
> > - __copy_m(_II __first, _II __last, _OI __result)
> > - {
> > - typedef typename iterator_traits<_II>::difference_type _Distance;
> > - for(_Distance __n = __last - __first; __n > 0; --__n)
> > - {
> > - *__result = std::move(*__first);
> > - ++__first;
> > - ++__result;
> > - }
> > - return __result;
> > - }
> > -
> > - template<typename _Tp, typename _Up>
> > - static void
> > - __assign_one(_Tp* __to, _Up* __from)
> > - { *__to = std::move(*__from); }
> > - };
> > -#endif
> > -
> > - template<bool _IsMove>
> > - struct __copy_move<_IsMove, true, random_access_iterator_tag>
> > - {
> > - template<typename _Tp, typename _Up>
> > - _GLIBCXX20_CONSTEXPR
> > - static _Up*
> > - __copy_m(_Tp* __first, _Tp* __last, _Up* __result)
> > - {
> > - const ptrdiff_t _Num = __last - __first;
> > - if (__builtin_expect(_Num > 1, true))
> > - __builtin_memmove(__result, __first, sizeof(_Tp) * _Num);
> > - else if (_Num == 1)
> > - std::__copy_move<_IsMove, false, random_access_iterator_tag>::
> > - __assign_one(__result, __first);
> > - return __result + _Num;
> > - }
> > - };
> > -
> > _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> >
> > template<typename _Tp, typename _Ref, typename _Ptr>
> > @@ -461,21 +358,127 @@ _GLIBCXX_END_NAMESPACE_CONTAINER
> > _GLIBCXX_STD_C::_Deque_iterator<_CharT, _CharT&, _CharT*>);
> > #endif // HOSTED
> >
> > - template<bool _IsMove, typename _II, typename _OI>
> > - _GLIBCXX20_CONSTEXPR
> > - inline _OI
> > - __copy_move_a2(_II __first, _II __last, _OI __result)
> > - {
> > - typedef typename iterator_traits<_II>::iterator_category
> _Category;
> > -#ifdef __cpp_lib_is_constant_evaluated
> > - if (std::is_constant_evaluated())
> > - return std::__copy_move<_IsMove, false, _Category>::
> > - __copy_m(__first, __last, __result);
> > +#if __cpp_lib_concepts
> > + // N.B. this is not the same as nothrow-forward-iterator, which
> doesn't
> > + // require noexcept operations, it just says it's undefined if they
> throw.
> > + // Here we require them to be actually noexcept.
> > + template<typename _Iter>
> > + concept __nothrow_contiguous_iterator
> > + = contiguous_iterator<_Iter> && requires (_Iter __i) {
> > + // If this operation can throw then the iterator could cause
> > + // the algorithm to exit early via an exception, in which case
> > + // we can't use memcpy.
> > + { *__i } noexcept;
> > + };
> > +
> > + template<typename _OutIter, typename _InIter, typename _Sent =
> _InIter>
> > + concept __memcpyable_iterators
> > + = __nothrow_contiguous_iterator<_OutIter>
> > + && __nothrow_contiguous_iterator<_InIter>
> > + && sized_sentinel_for<_Sent, _InIter>
> > + && requires (_OutIter __o, _InIter __i, _Sent __s) {
> > + requires !!__memcpyable<decltype(std::to_address(__o)),
> > +
> decltype(std::to_address(__i))>::__value;
> > + { __i != __s } noexcept;
> > + };
> > #endif
> > - return std::__copy_move<_IsMove, __memcpyable<_OI, _II>::__value,
> > - _Category>::__copy_m(__first, __last,
> __result);
> > +
> > +#if __cplusplus < 201103L
> > + // Used by __copy_move_a2, __copy_n_a and __copy_move_backward_a2 to
> > + // get raw pointers so that calls to __builtin_memmove will compile,
> > + // because C++98 can't use 'if constexpr' so statements that use
> memmove
> > + // with pointer arguments need to also compile for arbitrary iterator
> types.
> > + template<typename _Iter> __attribute__((__always_inline__))
> > + inline void* __ptr_or_null(_Iter) { return 0; }
> > + template<typename _Tp> __attribute__((__always_inline__))
> > + inline void* __ptr_or_null(_Tp* __p) { return (void*)__p; }
> > +# define _GLIBCXX_TO_ADDR(P) std::__ptr_or_null(P)
> > + // Used to advance output iterators (std::advance requires
> InputIterator).
> > + template<typename _Iter> __attribute__((__always_inline__))
> > + inline void __ptr_advance(_Iter&, ptrdiff_t) { }
> > + template<typename _Tp> __attribute__((__always_inline__))
> > + inline void __ptr_advance(_Tp*& __p, ptrdiff_t __n) { __p += __n; }
> > +# define _GLIBCXX_ADVANCE(P, N) std::__ptr_advance(P, N)
> > +#else
> > + // For C++11 mode the __builtin_memmove calls are guarded by 'if
> constexpr'
> > + // so we know the iterators used with memmove are guaranteed to be
> pointers.
> > +# define _GLIBCXX_TO_ADDR(P) P
> > +# define _GLIBCXX_ADVANCE(P, N) P += N
> > +#endif
> > +
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wc++17-extensions"
> > + template<bool _IsMove, typename _OutIter, typename _InIter>
> > + __attribute__((__always_inline__)) _GLIBCXX20_CONSTEXPR
> > + inline void
> > + __assign_one(_OutIter& __out, _InIter& __in)
> > + {
> > +#if __cplusplus >= 201103L
> > + if constexpr (_IsMove)
> > + *__out = std::move(*__in);
> > + else
> > +#endif
> > + *__out = *__in;
> > }
> >
> > + template<bool _IsMove, typename _InIter, typename _Sent, typename
> _OutIter>
> > + _GLIBCXX20_CONSTEXPR
> > + inline _OutIter
> > + __copy_move_a2(_InIter __first, _Sent __last, _OutIter __result)
> > + {
> > + typedef __decltype(*__first) _InRef;
> > + typedef __decltype(*__result) _OutRef;
> > + if _GLIBCXX_CONSTEXPR (!__is_trivially_assignable(_OutRef,
> _InRef))
> > + { } /* Skip the optimizations and use the loop at the end. */
> > +#ifdef __cpp_lib_is_constant_evaluated
> > + else if (std::is_constant_evaluated())
>
> Maybe check std::__is_constant_evaluated() instead since it's always
> defined?
>
For some reason I thought it wasn't defined for C++98, but it is, and it's
always_inline so won't add any runtime overhead.
> > + { } /* Skip the optimizations and use the loop at the end. */
> > +#endif
> > + else if _GLIBCXX_CONSTEXPR (__memcpyable<_OutIter,
> _InIter>::__value)
> > + {
> > + ptrdiff_t __n = std::distance(__first, __last);
> > + if (__builtin_expect(__n > 1, true))
>
> I guess [[__likely__]] can't be used here since this is a C++98 code
> path.
>
Yes. I think Jason recently made [[...]] attributes work in C++98 mode, but
I don't know if clang supports that.
> LGTM
>
> > + {
> > + __builtin_memmove(_GLIBCXX_TO_ADDR(__result),
> > + _GLIBCXX_TO_ADDR(__first),
> > + __n * sizeof(*__first));
> > + _GLIBCXX_ADVANCE(__result, __n);
> > + }
> > + else if (__n == 1)
> > + {
> > + std::__assign_one<_IsMove>(__result, __first);
> > + ++__result;
> > + }
> > + return __result;
> > + }
> > +#if __cpp_lib_concepts
> > + else if constexpr (__memcpyable_iterators<_OutIter, _InIter,
> _Sent>)
> > + {
> > + if (auto __n = __last - __first; __n > 1) [[likely]]
> > + {
> > + void* __dest = std::to_address(__result);
> > + const void* __src = std::to_address(__first);
> > + size_t __nbytes = __n * sizeof(iter_value_t<_InIter>);
> > + // Advance the iterators first, in case doing so throws.
> > + __result += __n;
> > + __first += __n;
> > + __builtin_memmove(__dest, __src, __nbytes);
> > + }
> > + else if (__n == 1)
> > + {
> > + std::__assign_one<_IsMove>(__result, __first);
> > + ++__result;
> > + }
> > + return __result;
> > + }
> > +#endif
> > +
> > + for (; __first != __last; ++__result, (void)++__first)
> > + std::__assign_one<_IsMove>(__result, __first);
> > + return __result;
> > + }
> > +#pragma GCC diagnostic pop
> > +
> > template<bool _IsMove,
> > typename _Tp, typename _Ref, typename _Ptr, typename _OI>
> > _OI
> > @@ -537,12 +540,56 @@ _GLIBCXX_END_NAMESPACE_CONTAINER
> > const ::__gnu_debug::_Safe_iterator<_IIte, _ISeq,
> _ICat>&,
> > const ::__gnu_debug::_Safe_iterator<_OIte, _OSeq,
> _OCat>&);
> >
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wc++17-extensions" // for if-constexpr
> > template<typename _InputIterator, typename _Size, typename
> _OutputIterator>
> > _GLIBCXX20_CONSTEXPR
> > _OutputIterator
> > __copy_n_a(_InputIterator __first, _Size __n, _OutputIterator
> __result,
> > bool)
> > {
> > + typedef __decltype(*__first) _InRef;
> > + typedef __decltype(*__result) _OutRef;
> > + if _GLIBCXX_CONSTEXPR (!__is_trivially_assignable(_OutRef,
> _InRef))
> > + { } /* Skip the optimizations and use the loop at the end. */
> > +#ifdef __cpp_lib_is_constant_evaluated
> > + else if (std::is_constant_evaluated())
> > + { } /* Skip the optimizations and use the loop at the end. */
> > +#endif
> > + else if _GLIBCXX_CONSTEXPR (__memcpyable<_OutputIterator,
> > + _InputIterator>::__value)
> > + {
> > + if (__builtin_expect(__n > 1, true))
> > + {
> > + __builtin_memmove(_GLIBCXX_TO_ADDR(__result),
> > + _GLIBCXX_TO_ADDR(__first),
> > + __n * sizeof(*__first));
> > + _GLIBCXX_ADVANCE(__result, __n);
> > + }
> > + else if (__n == 1)
> > + *__result++ = *__first;
> > + return __result;
> > + }
> > +#if __cpp_lib_concepts
> > + else if constexpr (__memcpyable_iterators<_OutputIterator,
> > + _InputIterator>)
> > + {
> > + if (__n > 1) [[likely]]
> > + {
> > + void* __dest = std::to_address(__result);
> > + const void* __src = std::to_address(__first);
> > + size_t __nbytes = __n * sizeof(iter_value_t<_InputIterator>);
> > + // Advance the iterators first, in case doing so throws.
> > + __result += __n;
> > + __first += __n;
> > + __builtin_memmove(__dest, __src, __nbytes);
> > + }
> > + else if (__n == 1)
> > + *__result++ = *__first;
> > + return __result;
> > + }
> > +#endif
> > +
> > if (__n > 0)
> > {
> > while (true)
> > @@ -557,6 +604,7 @@ _GLIBCXX_END_NAMESPACE_CONTAINER
> > }
> > return __result;
> > }
> > +#pragma GCC diagnostic pop
> >
> > #if _GLIBCXX_HOSTED
> > template<typename _CharT, typename _Size>
> > @@ -644,105 +692,69 @@ _GLIBCXX_END_NAMESPACE_CONTAINER
> > #define _GLIBCXX_MOVE3(_Tp, _Up, _Vp) std::copy(_Tp, _Up, _Vp)
> > #endif
> >
> > - template<bool _IsMove, bool _IsSimple, typename _Category>
> > - struct __copy_move_backward
> > - {
> > - template<typename _BI1, typename _BI2>
> > - _GLIBCXX20_CONSTEXPR
> > - static _BI2
> > - __copy_move_b(_BI1 __first, _BI1 __last, _BI2 __result)
> > - {
> > - while (__first != __last)
> > - *--__result = *--__last;
> > - return __result;
> > - }
> > - };
> > -
> > -#if __cplusplus >= 201103L
> > - template<typename _Category>
> > - struct __copy_move_backward<true, false, _Category>
> > - {
> > - template<typename _BI1, typename _BI2>
> > - _GLIBCXX20_CONSTEXPR
> > - static _BI2
> > - __copy_move_b(_BI1 __first, _BI1 __last, _BI2 __result)
> > - {
> > - while (__first != __last)
> > - *--__result = std::move(*--__last);
> > - return __result;
> > - }
> > - };
> > -#endif
> > -
> > - template<>
> > - struct __copy_move_backward<false, false,
> random_access_iterator_tag>
> > - {
> > - template<typename _BI1, typename _BI2>
> > - _GLIBCXX20_CONSTEXPR
> > - static _BI2
> > - __copy_move_b(_BI1 __first, _BI1 __last, _BI2 __result)
> > - {
> > - typename iterator_traits<_BI1>::difference_type
> > - __n = __last - __first;
> > - for (; __n > 0; --__n)
> > - *--__result = *--__last;
> > - return __result;
> > - }
> > - };
> > -
> > -#if __cplusplus >= 201103L
> > - template<>
> > - struct __copy_move_backward<true, false, random_access_iterator_tag>
> > - {
> > - template<typename _BI1, typename _BI2>
> > - _GLIBCXX20_CONSTEXPR
> > - static _BI2
> > - __copy_move_b(_BI1 __first, _BI1 __last, _BI2 __result)
> > - {
> > - typename iterator_traits<_BI1>::difference_type
> > - __n = __last - __first;
> > - for (; __n > 0; --__n)
> > - *--__result = std::move(*--__last);
> > - return __result;
> > - }
> > - };
> > -#endif
> > -
> > - template<bool _IsMove>
> > - struct __copy_move_backward<_IsMove, true,
> random_access_iterator_tag>
> > - {
> > - template<typename _Tp, typename _Up>
> > - _GLIBCXX20_CONSTEXPR
> > - static _Up*
> > - __copy_move_b(_Tp* __first, _Tp* __last, _Up* __result)
> > - {
> > - const ptrdiff_t _Num = __last - __first;
> > - if (__builtin_expect(_Num > 1, true))
> > - __builtin_memmove(__result - _Num, __first, sizeof(_Tp) *
> _Num);
> > - else if (_Num == 1)
> > - std::__copy_move<_IsMove, false, random_access_iterator_tag>::
> > - __assign_one(__result - 1, __first);
> > - return __result - _Num;
> > - }
> > - };
> > -
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wc++17-extensions"
> > template<bool _IsMove, typename _BI1, typename _BI2>
> > _GLIBCXX20_CONSTEXPR
> > inline _BI2
> > __copy_move_backward_a2(_BI1 __first, _BI1 __last, _BI2 __result)
> > {
> > - typedef typename iterator_traits<_BI1>::iterator_category
> _Category;
> > + typedef __decltype(*__first) _InRef;
> > + typedef __decltype(*__result) _OutRef;
> > + if _GLIBCXX_CONSTEXPR (!__is_trivially_assignable(_OutRef,
> _InRef))
> > + { } /* Skip the optimizations and use the loop at the end. */
> > #ifdef __cpp_lib_is_constant_evaluated
> > - if (std::is_constant_evaluated())
> > - return std::__copy_move_backward<_IsMove, false, _Category>::
> > - __copy_move_b(__first, __last, __result);
> > + else if (std::is_constant_evaluated())
> > + { } /* Skip the optimizations and use the loop at the end. */
> > #endif
> > - return std::__copy_move_backward<_IsMove,
> > - __memcpyable<_BI2, _BI1>::__value,
> > - _Category>::__copy_move_b(__first,
> > - __last,
> > - __result);
> > + else if _GLIBCXX_CONSTEXPR (__memcpyable<_BI2, _BI1>::__value)
> > + {
> > + ptrdiff_t __n = std::distance(__first, __last);
> > + std::advance(__result, -__n);
> > + if (__builtin_expect(__n > 1, true))
> > + {
> > + __builtin_memmove(_GLIBCXX_TO_ADDR(__result),
> > + _GLIBCXX_TO_ADDR(__first),
> > + __n * sizeof(*__first));
> > + }
> > + else if (__n == 1)
> > + std::__assign_one<_IsMove>(__result, __first);
> > + return __result;
> > + }
> > +#if __cpp_lib_concepts
> > + else if constexpr (__memcpyable_iterators<_BI2, _BI1>)
> > + {
> > + if (auto __n = __last - __first; __n > 1) [[likely]]
> > + {
> > + const void* __src = std::to_address(__first);
> > + // Advance the iterators first, in case doing so throws.
> > + __result -= __n;
> > + __first += __n;
> > + void* __dest = std::to_address(__result);
> > + size_t __nbytes = __n * sizeof(iter_value_t<_BI1>);
> > + __builtin_memmove(__dest, __src, __nbytes);
> > + }
> > + else if (__n == 1)
> > + {
> > + --__result;
> > + std::__assign_one<_IsMove>(__result, __first);
> > + }
> > + return __result;
> > + }
> > +#endif
> > +
> > + while (__first != __last)
> > + {
> > + --__last;
> > + --__result;
> > + std::__assign_one<_IsMove>(__result, __last);
> > + }
> > + return __result;
> > }
> > +#pragma GCC diagnostic pop
> > +
> > +#undef _GLIBCXX_TO_ADDR
> > +#undef _GLIBCXX_ADVANCE
> >
> > template<bool _IsMove, typename _BI1, typename _BI2>
> > _GLIBCXX20_CONSTEXPR
> > diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/114817.cc
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/114817.cc
> > new file mode 100644
> > index 00000000000..531b863e143
> > --- /dev/null
> > +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/114817.cc
> > @@ -0,0 +1,39 @@
> > +// { dg-do run { target c++11 } }
> > +
> > +// Bug libstdc++/114817 - Wrong codegen for std::copy of
> > +// "trivially copyable but not trivially assignable" type
> > +
> > +#include <memory>
> > +#include <testsuite_hooks.h>
> > +
> > +int constructions = 0;
> > +
> > +struct NonTrivialCons
> > +{
> > + NonTrivialCons() = default;
> > + NonTrivialCons(int v) : val(v) { }
> > + NonTrivialCons(const volatile NonTrivialCons&) = delete;
> > + template<class = void>
> > + NonTrivialCons(const NonTrivialCons& o)
> > + : val(o.val)
> > + {
> > + ++constructions;
> > + }
> > +
> > + int val;
> > +};
> > +
> > +static_assert(std::is_trivially_copyable<NonTrivialCons>::value);
> > +
> > +int main()
> > +{
> > + NonTrivialCons src[2]{1, 2};
> > + NonTrivialCons dst[2];
> > +#if __cplusplus < 201703L
> > + constructions = 0;
> > +#endif
> > + std::uninitialized_copy(src, src+2, dst);
> > + VERIFY( constructions == 2 );
> > + VERIFY( dst[0].val == src[0].val );
> > + VERIFY( dst[1].val == src[1].val );
> > +}
> > diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/114817.cc
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/114817.cc
> > new file mode 100644
> > index 00000000000..531b863e143
> > --- /dev/null
> > +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/114817.cc
> > @@ -0,0 +1,39 @@
> > +// { dg-do run { target c++11 } }
> > +
> > +// Bug libstdc++/114817 - Wrong codegen for std::copy of
> > +// "trivially copyable but not trivially assignable" type
> > +
> > +#include <memory>
> > +#include <testsuite_hooks.h>
> > +
> > +int constructions = 0;
> > +
> > +struct NonTrivialCons
> > +{
> > + NonTrivialCons() = default;
> > + NonTrivialCons(int v) : val(v) { }
> > + NonTrivialCons(const volatile NonTrivialCons&) = delete;
> > + template<class = void>
> > + NonTrivialCons(const NonTrivialCons& o)
> > + : val(o.val)
> > + {
> > + ++constructions;
> > + }
> > +
> > + int val;
> > +};
> > +
> > +static_assert(std::is_trivially_copyable<NonTrivialCons>::value);
> > +
> > +int main()
> > +{
> > + NonTrivialCons src[2]{1, 2};
> > + NonTrivialCons dst[2];
> > +#if __cplusplus < 201703L
> > + constructions = 0;
> > +#endif
> > + std::uninitialized_copy(src, src+2, dst);
> > + VERIFY( constructions == 2 );
> > + VERIFY( dst[0].val == src[0].val );
> > + VERIFY( dst[1].val == src[1].val );
> > +}
> > diff --git a/libstdc++-v3/testsuite/25_algorithms/copy/114817.cc
> b/libstdc++-v3/testsuite/25_algorithms/copy/114817.cc
> > new file mode 100644
> > index 00000000000..b5fcc6bb037
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/25_algorithms/copy/114817.cc
> > @@ -0,0 +1,38 @@
> > +// { dg-do run { target c++11 } }
> > +
> > +// Bug libstdc++/114817 - Wrong codegen for std::copy of
> > +// "trivially copyable but not trivially assignable" type
> > +
> > +#include <algorithm>
> > +#include <testsuite_hooks.h>
> > +
> > +int assignments = 0;
> > +
> > +struct NonTrivialAssignment
> > +{
> > + NonTrivialAssignment(int v) : val(v) { }
> > + NonTrivialAssignment(const NonTrivialAssignment&) = default;
> > + void operator=(const volatile NonTrivialAssignment&) = delete;
> > + template<class = void>
> > + NonTrivialAssignment&
> > + operator=(const NonTrivialAssignment& o)
> > + {
> > + ++assignments;
> > + val = o.val;
> > + return *this;
> > + }
> > +
> > + int val;
> > +};
> > +
> > +static_assert(std::is_trivially_copyable<NonTrivialAssignment>::value);
> > +
> > +int main()
> > +{
> > + NonTrivialAssignment src[2]{1, 2};
> > + NonTrivialAssignment dst[2]{3, 4};
> > + std::copy(src, src+2, dst);
> > + VERIFY( assignments == 2 );
> > + VERIFY( dst[0].val == src[0].val );
> > + VERIFY( dst[1].val == src[1].val );
> > +}
> > diff --git a/libstdc++-v3/testsuite/25_algorithms/copy/115444.cc
> b/libstdc++-v3/testsuite/25_algorithms/copy/115444.cc
> > new file mode 100644
> > index 00000000000..fa629abea5f
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/25_algorithms/copy/115444.cc
> > @@ -0,0 +1,93 @@
> > +// { dg-do run }
> > +// { dg-require-normal-mode "debug mode checks use operator-(Iter,
> Iter)" }
> > +
> > +#include <algorithm>
> > +#include <iterator>
> > +#include <testsuite_hooks.h>
> > +
> > +const int g = 0;
> > +
> > +struct Iter {
> > + typedef long difference_type;
> > + typedef int value_type;
> > + typedef const int& reference;
> > + typedef const int* pointer;
> > +#if __cpp_lib_concepts
> > + using iterator_category = std::contiguous_iterator_tag;
> > +#else
> > + typedef std::random_access_iterator_tag iterator_category;
> > +#endif
> > +
> > + Iter(const int* p = 0, const int* limit = 0) : ptr(p), limit(limit) {
> }
> > +
> > + const int& operator*() const {
> > +#ifdef __cpp_exceptions
> > + if (ptr == limit)
> > + throw 1;
> > +#endif
> > + return *ptr;
> > + }
> > + const int* operator->() const { return ptr; }
> > + const int& operator[](long n) const { return ptr[n]; }
> > +
> > + Iter& operator++() { ++ptr; return *this; }
> > + Iter operator++(int) { Iter tmp = *this; ++ptr; return tmp; }
> > + Iter& operator--() { --ptr; return *this; }
> > + Iter operator--(int) { Iter tmp = *this; --ptr; return tmp; }
> > +
> > + Iter& operator+=(int n) { ptr += n; return *this; }
> > + Iter& operator-=(int n) { ptr -= n; return *this; }
> > +
> > + friend Iter operator+(int n, Iter it) { return it += n; }
> > + friend Iter operator+(Iter it, int n) { return it += n; }
> > + friend Iter operator-(Iter it, int n) { return it -= n; }
> > +
> > + bool operator==(const Iter& it) const { return ptr == it.ptr; }
> > + bool operator!=(const Iter& it) const { return ptr != it.ptr; }
> > + bool operator<(const Iter& it) const { return ptr < it.ptr; }
> > + bool operator>(const Iter& it) const { return ptr > it.ptr; }
> > + bool operator<=(const Iter& it) const { return ptr <= it.ptr; }
> > + bool operator>=(const Iter& it) const { return ptr >= it.ptr; }
> > +
> > + // std::copy should not need to take the difference between two
> iterators:
> > + friend int operator-(Iter, Iter) { VERIFY( ! "operator- called" ); }
> > +
> > +private:
> > + const int* ptr;
> > + const int* limit;
> > +};
> > +
> > +void
> > +test_pr115444_no_difference()
> > +{
> > + int from = 1;
> > + int to = 0;
> > + Iter iter(&from);
> > + // This should not use operator-(Iter, Iter)
> > + std::copy(iter, iter+1, &to);
> > +}
> > +
> > +void
> > +test_pr115444_exceptional()
> > +{
> > +#if __cpp_exceptions
> > + int from[3] = { 1, 2, 3 };
> > + int to[3] = { -1, -1, -1 };
> > + Iter iter(from, from+2);
> > + try {
> > + std::copy(iter, iter + 3, to);
> > + } catch (int) {
> > + }
> > + // std::copy should exit via exception on third dereference.
> > + // This precludes using memcpy or memmove to optimize the copying.
> > + VERIFY( to[0] == 1 );
> > + VERIFY( to[1] == 2 );
> > + VERIFY( to[2] == -1 );
> > +#endif
> > +}
> > +
> > +int main()
> > +{
> > + test_pr115444_no_difference();
> > + test_pr115444_exceptional();
> > +}
> > diff --git a/libstdc++-v3/testsuite/25_algorithms/copy_n/114817.cc
> b/libstdc++-v3/testsuite/25_algorithms/copy_n/114817.cc
> > new file mode 100644
> > index 00000000000..09e181f3fd0
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/25_algorithms/copy_n/114817.cc
> > @@ -0,0 +1,38 @@
> > +// { dg-do run { target c++11 } }
> > +
> > +// Bug libstdc++/114817 - Wrong codegen for std::copy of
> > +// "trivially copyable but not trivially assignable" type
> > +
> > +#include <algorithm>
> > +#include <testsuite_hooks.h>
> > +
> > +int assignments = 0;
> > +
> > +struct NonTrivialAssignment
> > +{
> > + NonTrivialAssignment(int v) : val(v) { }
> > + NonTrivialAssignment(const NonTrivialAssignment&) = default;
> > + void operator=(const volatile NonTrivialAssignment&) = delete;
> > + template<class = void>
> > + NonTrivialAssignment&
> > + operator=(const NonTrivialAssignment& o)
> > + {
> > + ++assignments;
> > + val = o.val;
> > + return *this;
> > + }
> > +
> > + int val;
> > +};
> > +
> > +static_assert(std::is_trivially_copyable<NonTrivialAssignment>::value);
> > +
> > +int main()
> > +{
> > + NonTrivialAssignment src[2]{1, 2};
> > + NonTrivialAssignment dst[2]{3, 4};
> > + std::copy_n(src, 2, dst);
> > + VERIFY( assignments == 2 );
> > + VERIFY( dst[0].val == src[0].val );
> > + VERIFY( dst[1].val == src[1].val );
> > +}
> > --
> > 2.46.2
> >
> >
>
>