On Thu, 17 Oct 2024, 03:04 Patrick Palka, <ppa...@redhat.com> 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 > > > > > >