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?
> + { } /* 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.
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
>
>