On Tue, 15 Oct 2024, Jonathan Wakely wrote:
> This is v2 of
> https://gcc.gnu.org/pipermail/gcc-patches/2024-October/665246.html
> fixing some thinkos in uninitialized_{fill,fill_n}. We don't need to
> worry about overwriting tail-padding in those algos, because we only use
> memset for 1-byte integer types. So they have no tail padding that can
> be reused anyway! So this changes __n > 1 to __n > 0 in a few places
> (which fixes the problem that it was not actually filling anything for
> the n==1 cases).
>
> Also simplify std::__to_address(__result++) to just __result++ because
> we already have a pointer, and use std::to_address(result++) for a C++20
> std::contiguous_iterator case, instead of addressof(*result++).
>
> Tested x86_64-linux.
>
> -- >8 --
>
> This refactors the std::uninitialized_copy, std::uninitialized_fill and
> std::uninitialized_fill_n algorithms to directly perform memcpy/memset
> optimizations instead of dispatching to std::copy/std::fill/std::fill_n.
>
> The reasons for this are:
>
> - Use 'if constexpr' to simplify and optimize compilation throughput, so
> dispatching to specialized class templates is only needed for C++98
> mode.
> - Relax the conditions for using memcpy/memset, because the C++20 rules
> on implicit-lifetime types mean that we can rely on memcpy to begin
> lifetimes of trivially copyable types. We don't need to require
> trivially default constructible, so don't need to limit the
> optimization to trivial types. See PR 68350 for more details.
> - The conditions on non-overlapping ranges are stronger for
> std::uninitialized_copy than for std::copy so we can use memcpy instead
> of memmove, which might be a minor optimization.
> - Avoid including <bits/stl_algobase.h> in <bits/stl_uninitialized.h>.
> It only needs some iterator utilities from that file now, which belong
> in <bits/stl_iterator.h> anyway, so this moves them there.
>
> Several tests need changes to the diagnostics matched by dg-error
> because we no longer use the __constructible() function that had a
> static assert in. Now we just get straightforward errors for attempting
> to use a deleted constructor.
>
> Two tests needed more signficant changes to the actual expected results
> of executing the tests, because they were checking for old behaviour
> which was incorrect according to the standard.
> 20_util/specialized_algorithms/uninitialized_copy/64476.cc was expecting
> std::copy to be used for a call to std::uninitialized_copy involving two
> trivially copyable types. That was incorrect behaviour, because a
> non-trivial constructor should have been used, but using std::copy used
> trivial default initialization followed by assignment.
> 20_util/specialized_algorithms/uninitialized_fill_n/sizes.cc was testing
> the behaviour with a non-integral Size passed to uninitialized_fill_n,
> but I wrote the test looking at the requirements of uninitialized_copy_n
> which are not the same as uninitialized_fill_n. The former uses --n and
> tests n > 0, but the latter just tests n-- (which will never be false
> for a floating-point value with a fractional part).
>
> libstdc++-v3/ChangeLog:
>
> PR libstdc++/68350
> PR libstdc++/93059
> * include/bits/stl_algobase.h (__niter_base, __niter_wrap): Move
> to ...
> * include/bits/stl_iterator.h: ... here.
> * include/bits/stl_uninitialized.h (__check_constructible)
> (_GLIBCXX_USE_ASSIGN_FOR_INIT): Remove.
> [C++98] (__unwrappable_niter): New trait.
> (__uninitialized_copy<true>): Replace use of std::copy.
> (uninitialized_copy): Fix Doxygen comments. Open-code memcpy
> optimization for C++11 and later.
> (__uninitialized_fill<true>): Replace use of std::fill.
> (uninitialized_fill): Fix Doxygen comments. Open-code memset
> optimization for C++11 and later.
> (__uninitialized_fill_n<true>): Replace use of std::fill_n.
> (uninitialized_fill_n): Fix Doxygen comments. Open-code memset
> optimization for C++11 and later.
> * testsuite/20_util/specialized_algorithms/uninitialized_copy/64476.cc:
> Adjust expected behaviour to match what the standard specifies.
> *
> testsuite/20_util/specialized_algorithms/uninitialized_fill_n/sizes.cc:
> Likewise.
> * testsuite/20_util/specialized_algorithms/uninitialized_copy/1.cc:
> Adjust dg-error directives.
> * testsuite/20_util/specialized_algorithms/uninitialized_copy/89164.cc:
> Likewise.
> *
> testsuite/20_util/specialized_algorithms/uninitialized_copy_n/89164.cc:
> Likewise.
> * testsuite/20_util/specialized_algorithms/uninitialized_fill/89164.cc:
> Likewise.
> *
> testsuite/20_util/specialized_algorithms/uninitialized_fill_n/89164.cc:
> Likewise.
> * testsuite/23_containers/vector/cons/89164.cc: Likewise.
> * testsuite/23_containers/vector/cons/89164_c++17.cc: Likewise.
> ---
> libstdc++-v3/include/bits/stl_algobase.h | 45 --
> libstdc++-v3/include/bits/stl_iterator.h | 54 +++
> libstdc++-v3/include/bits/stl_uninitialized.h | 385 +++++++++++++-----
> .../uninitialized_copy/1.cc | 3 +-
> .../uninitialized_copy/64476.cc | 6 +-
> .../uninitialized_copy/89164.cc | 3 +-
> .../uninitialized_copy_n/89164.cc | 3 +-
> .../uninitialized_fill/89164.cc | 3 +-
> .../uninitialized_fill_n/89164.cc | 3 +-
> .../uninitialized_fill_n/sizes.cc | 22 +-
> .../23_containers/vector/cons/89164.cc | 5 +-
> .../23_containers/vector/cons/89164_c++17.cc | 3 +-
> 12 files changed, 383 insertions(+), 152 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/stl_algobase.h
> b/libstdc++-v3/include/bits/stl_algobase.h
> index 384e5fdcdc9..751b7ad119b 100644
> --- a/libstdc++-v3/include/bits/stl_algobase.h
> +++ b/libstdc++-v3/include/bits/stl_algobase.h
> @@ -308,51 +308,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> return __a;
> }
>
> - // Fallback implementation of the function in bits/stl_iterator.h used to
> - // remove the __normal_iterator wrapper. See copy, fill, ...
> - template<typename _Iterator>
> - _GLIBCXX20_CONSTEXPR
> - inline _Iterator
> - __niter_base(_Iterator __it)
> -
> _GLIBCXX_NOEXCEPT_IF(std::is_nothrow_copy_constructible<_Iterator>::value)
> - { return __it; }
> -
> -#if __cplusplus < 201103L
> - template<typename _Ite, typename _Seq>
> - _Ite
> - __niter_base(const ::__gnu_debug::_Safe_iterator<_Ite, _Seq,
> - std::random_access_iterator_tag>&);
> -
> - template<typename _Ite, typename _Cont, typename _Seq>
> - _Ite
> - __niter_base(const ::__gnu_debug::_Safe_iterator<
> - ::__gnu_cxx::__normal_iterator<_Ite, _Cont>, _Seq,
> - std::random_access_iterator_tag>&);
> -#else
> - template<typename _Ite, typename _Seq>
> - _GLIBCXX20_CONSTEXPR
> - decltype(std::__niter_base(std::declval<_Ite>()))
> - __niter_base(const ::__gnu_debug::_Safe_iterator<_Ite, _Seq,
> - std::random_access_iterator_tag>&)
> - noexcept(std::is_nothrow_copy_constructible<_Ite>::value);
> -#endif
> -
> - // Reverse the __niter_base transformation to get a
> - // __normal_iterator back again (this assumes that __normal_iterator
> - // is only used to wrap random access iterators, like pointers).
> - template<typename _From, typename _To>
> - _GLIBCXX20_CONSTEXPR
> - inline _From
> - __niter_wrap(_From __from, _To __res)
> - { return __from + (std::__niter_base(__res) -
> std::__niter_base(__from)); }
> -
> - // No need to wrap, iterator already has the right type.
> - template<typename _Iterator>
> - _GLIBCXX20_CONSTEXPR
> - inline _Iterator
> - __niter_wrap(const _Iterator&, _Iterator __res)
> - { return __res; }
> -
> // 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.)
> diff --git a/libstdc++-v3/include/bits/stl_iterator.h
> b/libstdc++-v3/include/bits/stl_iterator.h
> index 28a600c81cb..85b98ffff61 100644
> --- a/libstdc++-v3/include/bits/stl_iterator.h
> +++ b/libstdc++-v3/include/bits/stl_iterator.h
> @@ -1338,10 +1338,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace
>
> +namespace __gnu_debug
> +{
> + template<typename _Iterator, typename _Sequence, typename _Category>
> + class _Safe_iterator;
> +}
> +
> namespace std _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> + // Unwrap a __normal_iterator to get the underlying iterator
> + // (usually a pointer)
> template<typename _Iterator, typename _Container>
> _GLIBCXX20_CONSTEXPR
> _Iterator
> @@ -1349,6 +1357,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> _GLIBCXX_NOEXCEPT_IF(std::is_nothrow_copy_constructible<_Iterator>::value)
> { return __it.base(); }
>
> + // Fallback implementation of the function in bits/stl_iterator.h used to
> + // remove the __normal_iterator wrapper. See std::copy, std::fill, etc.
> + template<typename _Iterator>
> + _GLIBCXX20_CONSTEXPR
> + inline _Iterator
> + __niter_base(_Iterator __it)
> +
> _GLIBCXX_NOEXCEPT_IF(std::is_nothrow_copy_constructible<_Iterator>::value)
> + { return __it; }
> +
> + // Overload for _Safe_iterator needs to be declared before __niter_base
> uses.
Do we also need to declare __niter_base(move_iterator) earlier in this
file so that name lookup finds it when calling __niter_base ...
> +#if __cplusplus < 201103L
> + template<typename _Ite, typename _Seq>
> + _Ite
> + __niter_base(const ::__gnu_debug::_Safe_iterator<_Ite, _Seq,
> + std::random_access_iterator_tag>&);
> +
> + template<typename _Ite, typename _Cont, typename _Seq>
> + _Ite
> + __niter_base(const ::__gnu_debug::_Safe_iterator<
> + ::__gnu_cxx::__normal_iterator<_Ite, _Cont>, _Seq,
> + std::random_access_iterator_tag>&);
> +#else
> + template<typename _Ite, typename _Seq>
> + _GLIBCXX20_CONSTEXPR
> + decltype(std::__niter_base(std::declval<_Ite>()))
... here and ...
> + __niter_base(const ::__gnu_debug::_Safe_iterator<_Ite, _Seq,
> + std::random_access_iterator_tag>&)
> + noexcept(std::is_nothrow_copy_constructible<_Ite>::value);
> +#endif
> +
> + // Reverse the __niter_base transformation to get a
> + // __normal_iterator back again (this assumes that __normal_iterator
> + // is only used to wrap random access iterators, like pointers).
> + template<typename _From, typename _To>
> + _GLIBCXX20_CONSTEXPR
> + inline _From
> + __niter_wrap(_From __from, _To __res)
> + { return __from + (std::__niter_base(__res) -
> std::__niter_base(__from)); }
... here?
> +
> + // No need to wrap, iterator already has the right type.
> + template<typename _Iterator>
> + _GLIBCXX20_CONSTEXPR
> + inline _Iterator
> + __niter_wrap(const _Iterator&, _Iterator __res)
> + { return __res; }
> +
> #if __cplusplus >= 201103L && __cplusplus <= 201703L
> // Need to overload __to_address because the pointer_traits primary
> template
> // will deduce element_type of __normal_iterator<T*, C> as T* rather than
> T.
> diff --git a/libstdc++-v3/include/bits/stl_uninitialized.h
> b/libstdc++-v3/include/bits/stl_uninitialized.h
> index f663057b1a1..ec980d66ccf 100644
> --- a/libstdc++-v3/include/bits/stl_uninitialized.h
> +++ b/libstdc++-v3/include/bits/stl_uninitialized.h
> @@ -57,16 +57,16 @@
> #define _STL_UNINITIALIZED_H 1
>
> #if __cplusplus >= 201103L
> -#include <type_traits>
> +# include <type_traits>
> +# include <bits/ptr_traits.h> // __to_address
> +# include <bits/stl_pair.h> // pair
> #endif
>
> -#include <bits/stl_algobase.h> // copy
> +#include <bits/cpp_type_traits.h> // __is_pointer
> +#include <bits/stl_iterator_base_funcs.h> // distance, advance
> +#include <bits/stl_iterator.h> // __niter_base
> #include <ext/alloc_traits.h> // __alloc_traits
>
> -#if __cplusplus >= 201703L
> -#include <bits/stl_pair.h>
> -#endif
> -
> namespace std _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
> @@ -77,36 +77,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> /// @cond undocumented
>
> -#if __cplusplus >= 201103L
> - template<typename _ValueType, typename _Tp>
> - constexpr bool
> - __check_constructible()
> - {
> - // Trivial types can have deleted constructors, but std::copy etc.
> - // only use assignment (or memmove) not construction, so we need an
> - // explicit check that construction from _Tp is actually valid,
> - // otherwise some ill-formed uses of std::uninitialized_xxx would
> - // compile without errors. This gives a nice clear error message.
> - static_assert(is_constructible<_ValueType, _Tp>::value,
> - "result type must be constructible from input type");
> -
> - return true;
> - }
> -
> -// If the type is trivial we don't need to construct it, just assign to it.
> -// But trivial types can still have deleted or inaccessible assignment,
> -// so don't try to use std::copy or std::fill etc. if we can't assign.
> -# define _GLIBCXX_USE_ASSIGN_FOR_INIT(T, U) \
> - __is_trivial(T) && __is_assignable(T&, U) \
> - && std::__check_constructible<T, U>()
> -#else
> -// No need to check if is_constructible<T, U> for C++98. Trivial types have
> -// no user-declared constructors, so if the assignment is valid, construction
> -// should be too.
> -# define _GLIBCXX_USE_ASSIGN_FOR_INIT(T, U) \
> - __is_trivial(T) && __is_assignable(T&, U)
> -#endif
> -
> template<typename _ForwardIterator, typename _Alloc = void>
> struct _UninitDestroyGuard
> {
> @@ -160,6 +130,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _UninitDestroyGuard(const _UninitDestroyGuard&);
> };
>
> + // This is the default implementation of std::uninitialized_copy.
> template<typename _InputIterator, typename _ForwardIterator>
> _GLIBCXX20_CONSTEXPR
> _ForwardIterator
> @@ -173,7 +144,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> return __result;
> }
>
> - template<bool _TrivialValueTypes>
> +#if __cplusplus < 201103L
> +
> + // True if we can unwrap _Iter to get a pointer by using std::__niter_base.
> + template<typename _Iter>
> + struct __unwrappable_niter
> + {
> + template<typename> struct __is_ptr { enum { __value = 0 }; };
> + template<typename _Tp> struct __is_ptr<_Tp*> { enum { __value = 1 }; };
> +
> + typedef __decltype(std::__niter_base(*(_Iter*)0)) _Base;
> +
> + enum { __value = __is_ptr<_Base>::__value };
> + };
It might be slightly cheaper to define this without the nested class
template as:
template<typename _Iter, typename _Base =
__decltype(std::__niter_base(*(_Iter*)0))>
struct __unwrappable_niter
{ enum { __value = false }; };
template<typename _Iter, typename _Tp>
struct __unwrappable_niter<_Iter, _Tp*>
{ enum { __value = true }; };
> +
> + // Use template specialization for C++98 when 'if constexpr' can't be used.
> + template<bool _CanMemcpy>
> struct __uninitialized_copy
> {
> template<typename _InputIterator, typename _ForwardIterator>
> @@ -186,53 +172,150 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<>
> struct __uninitialized_copy<true>
> {
> + // Overload for generic iterators.
> template<typename _InputIterator, typename _ForwardIterator>
> static _ForwardIterator
> __uninit_copy(_InputIterator __first, _InputIterator __last,
> _ForwardIterator __result)
> - { return std::copy(__first, __last, __result); }
> - };
> + {
> + if (__unwrappable_niter<_InputIterator>::__value
> + && __unwrappable_niter<_ForwardIterator>::__value)
> + {
> + __uninit_copy(std::__niter_base(__first),
> + std::__niter_base(__last),
> + std::__niter_base(__result));
> + std::advance(__result, std::distance(__first, __last));
> + return __result;
> + }
> + else
> + return std::__do_uninit_copy(__first, __last, __result);
> + }
>
> + // Overload for pointers.
> + template<typename _Tp, typename _Up>
> + static _Up*
> + __uninit_copy(_Tp* __first, _Tp* __last, _Up* __result)
> + {
> + // Ensure that we don't successfully memcpy in cases that should be
> + // ill-formed because is_constructible<_Up, _Tp&> is false.
> + typedef __typeof__(static_cast<_Up>(*__first)) __check
> + __attribute__((__unused__));
> +
> + if (const ptrdiff_t __n = __last - __first)
Do we have to worry about the __n == 1 case here like in the C++11 code path?
> + {
> + __builtin_memcpy(__result, __first, __n * sizeof(_Tp));
> + __result += __n;
> + }
> + return __result;
> + }
> + };
> +#endif
> /// @endcond
>
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wc++17-extensions"
> /**
> * @brief Copies the range [first,last) into result.
> * @param __first An input iterator.
> * @param __last An input iterator.
> - * @param __result An output iterator.
> - * @return __result + (__first - __last)
> + * @param __result A forward iterator.
> + * @return __result + (__last - __first)
> *
> - * Like copy(), but does not require an initialized output range.
> + * Like std::copy, but does not require an initialized output range.
> */
> template<typename _InputIterator, typename _ForwardIterator>
> inline _ForwardIterator
> uninitialized_copy(_InputIterator __first, _InputIterator __last,
> _ForwardIterator __result)
> {
> + // We can use memcpy to copy the ranges under these conditions:
> + //
> + // _ForwardIterator and _InputIterator are both contiguous iterators,
> + // so that we can turn them into pointers to pass to memcpy.
> + // Before C++20 we can't detect all contiguous iterators, so we only
> + // handle built-in pointers and __normal_iterator<T*, C> types.
> + //
> + // The value types of both iterators are trivially-copyable types,
> + // so that memcpy is not undefined and can begin the lifetime of
> + // new objects in the output range.
> + //
> + // Finally, memcpy from the source type, S, to the destination type, D,
> + // must give the same value as initialization of D from S would give.
> + // We require is_trivially_constructible<D, S> to be true, but that is
> + // not sufficient. Some cases of trivial initialization are not just a
> + // bitwise copy, even when sizeof(D) == sizeof(S),
> + // e.g. bit_cast<unsigned>(1.0f) != 1u because the corresponding bits
> + // of the value representations do not have the same meaning.
> + // We cannot tell when this condition is true in general,
> + // so we rely on the __memcpyable trait.
> +
> +#if __cplusplus >= 201103L
> + using _Dest = decltype(std::__niter_base(__result));
> + using _Src = decltype(std::__niter_base(__first));
> + using _ValT = typename iterator_traits<_ForwardIterator>::value_type;
> +
> + if constexpr (!__is_trivially_constructible(_ValT, decltype(*__first)))
> + return std::__do_uninit_copy(__first, __last, __result);
> + else if constexpr (__memcpyable<_Dest, _Src>::__value)
> + {
> + ptrdiff_t __n = __last - __first;
> + if (__builtin_expect(__n > 1, true))
Could we use [[__likely__]] instead?
> + {
> + using _ValT = typename remove_pointer<_Src>::type;
> + __builtin_memcpy(std::__niter_base(__result),
> + std::__niter_base(__first),
> + __n * sizeof(_ValT));
> + __result += __n;
> + }
> + else if (__n == 1) // memcpy could overwrite tail padding
> + std::_Construct(__result++, *__first);
> + return __result;
> + }
> +#if __cpp_lib_concepts
> + else if constexpr (contiguous_iterator<_ForwardIterator>
> + && contiguous_iterator<_InputIterator>)
> + {
> + using _DestPtr = decltype(std::to_address(__result));
> + using _SrcPtr = decltype(std::to_address(__first));
> + if constexpr (__memcpyable<_DestPtr, _SrcPtr>::__value)
> + {
> + 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(remove_pointer_t<_DestPtr>);
> + __builtin_memmove(__dest, __src, __nbytes);
Why do we need to use memmove instead of memcpy here?
> + __result += __n;
> + }
> + else if (__n == 1) // memcpy could overwrite tail padding
> + std::construct_at(std::to_address(__result++), *__first);
> + return __result;
> + }
> + else
> + return std::__do_uninit_copy(__first, __last, __result);
> + }
> +#endif
> + else
> + return std::__do_uninit_copy(__first, __last, __result);
> +#else // C++98
> typedef typename iterator_traits<_InputIterator>::value_type
> _ValueType1;
> typedef typename iterator_traits<_ForwardIterator>::value_type
> _ValueType2;
>
> - // _ValueType1 must be trivially-copyable to use memmove, so don't
> - // bother optimizing to std::copy if it isn't.
> - // XXX Unnecessary because std::copy would check it anyway?
> - const bool __can_memmove = __is_trivial(_ValueType1);
> + const bool __can_memcpy
> + = __memcpyable<_ValueType1*, _ValueType2*>::__value
> + && __is_trivially_constructible(_ValueType2, __decltype(*__first));
>
> -#if __cplusplus < 201103L
> - typedef typename iterator_traits<_InputIterator>::reference _From;
> -#else
> - using _From = decltype(*__first);
> + return __uninitialized_copy<__can_memcpy>::
> + __uninit_copy(__first, __last, __result);
> #endif
> - const bool __assignable
> - = _GLIBCXX_USE_ASSIGN_FOR_INIT(_ValueType2, _From);
> -
> - return std::__uninitialized_copy<__can_memmove && __assignable>::
> - __uninit_copy(__first, __last, __result);
> }
> +#pragma GCC diagnostic pop
>
> /// @cond undocumented
>
> + // This is the default implementation of std::uninitialized_fill.
> template<typename _ForwardIterator, typename _Tp>
> _GLIBCXX20_CONSTEXPR void
> __do_uninit_fill(_ForwardIterator __first, _ForwardIterator __last,
> @@ -244,12 +327,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> __guard.release();
> }
>
> - template<bool _TrivialValueType>
> +#if __cplusplus < 201103L
> + // Use template specialization for C++98 when 'if constexpr' can't be used.
> + template<bool _CanMemset>
> struct __uninitialized_fill
> {
> template<typename _ForwardIterator, typename _Tp>
> - static void
> - __uninit_fill(_ForwardIterator __first, _ForwardIterator __last,
> + static void
> + __uninit_fill(_ForwardIterator __first, _ForwardIterator __last,
> const _Tp& __x)
> { std::__do_uninit_fill(__first, __last, __x); }
> };
> @@ -257,56 +342,129 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<>
> struct __uninitialized_fill<true>
> {
> + // Overload for generic iterators.
> template<typename _ForwardIterator, typename _Tp>
> - static void
> - __uninit_fill(_ForwardIterator __first, _ForwardIterator __last,
> + static void
> + __uninit_fill(_ForwardIterator __first, _ForwardIterator __last,
> const _Tp& __x)
> - { std::fill(__first, __last, __x); }
> - };
> + {
> + if (__unwrappable_niter<_ForwardIterator>::__value)
> + __uninit_fill(std::__niter_base(__first),
> + std::__niter_base(__last),
> + __x);
> + else
> + std::__do_uninit_copy(__first, __last, __x);
> + }
>
> + // Overload for pointers.
> + template<typename _Up, typename _Tp>
> + static void
> + __uninit_fill(_Up* __first, _Up* __last, const _Tp& __x)
> + {
> + // Ensure that we don't successfully memset in cases that should be
> + // ill-formed because is_constructible<_Up, const _Tp&> is false.
> + typedef __typeof__(static_cast<_Up>(__x)) __check
> + __attribute__((__unused__));
> +
> + if (__first != __last)
> + __builtin_memset(__first, (int)__x, __last - __first);
Maybe (unsigned char)__x for consistency with the C++11 code path?
> + }
> + };
> +#endif
> /// @endcond
>
> /**
> * @brief Copies the value x into the range [first,last).
> - * @param __first An input iterator.
> - * @param __last An input iterator.
> + * @param __first A forward iterator.
> + * @param __last A forward iterator.
> * @param __x The source value.
> * @return Nothing.
> *
> - * Like fill(), but does not require an initialized output range.
> + * Like std::fill, but does not require an initialized output range.
> */
> template<typename _ForwardIterator, typename _Tp>
> inline void
> uninitialized_fill(_ForwardIterator __first, _ForwardIterator __last,
> const _Tp& __x)
> {
> + // We would like to use memset to optimize this loop when possible.
> + // As for std::uninitialized_copy, the optimization requires
> + // contiguous iterators and trivially copyable value types,
> + // with the additional requirement that sizeof(_Tp) == 1 because
> + // memset only writes single bytes.
> +
> + // FIXME: We could additionally enable this for 1-byte enums.
> + // Maybe any 1-byte Val if is_trivially_constructible<Val, const T&>?
> +
> typedef typename iterator_traits<_ForwardIterator>::value_type
> _ValueType;
>
> - // Trivial types do not need a constructor to begin their lifetime,
> - // so try to use std::fill to benefit from its memset optimization.
> - const bool __can_fill
> - = _GLIBCXX_USE_ASSIGN_FOR_INIT(_ValueType, const _Tp&);
> +#if __cplusplus >= 201103L
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wc++17-extensions"
> + if constexpr (__is_byte<_ValueType>::__value)
> + if constexpr (is_same<_ValueType, _Tp>::value
> + || is_integral<_Tp>::value)
> + {
> + using _BasePtr = decltype(std::__niter_base(__first));
> + if constexpr (is_pointer<_BasePtr>::value)
> + {
> + void* __dest = std::__niter_base(__first);
> + ptrdiff_t __n = __last - __first;
> + if (__builtin_expect(__n > 0, true))
> + __builtin_memset(__dest, (unsigned char)__x, __n);
> + return;
> + }
> +#if __cpp_lib_concepts
> + else if constexpr (contiguous_iterator<_ForwardIterator>)
> + {
> + auto __dest = std::__to_address(__first);
> + auto __n = __last - __first;
> + if (__builtin_expect(__n > 0, true))
> + __builtin_memset(__dest, (unsigned char)__x, __n);
> + return;
> + }
> +#endif
> + }
> + std::__do_uninit_fill(__first, __last, __x);
> +#pragma GCC diagnostic pop
> +#else // C++98
> + const bool __can_memset = __is_byte<_ValueType>::__value
> + && __is_integer<_Tp>::__value;
>
> - std::__uninitialized_fill<__can_fill>::
> - __uninit_fill(__first, __last, __x);
> + __uninitialized_fill<__can_memset>::__uninit_fill(__first, __last,
> __x);
> +#endif
> }
>
> /// @cond undocumented
>
> + // This is the default implementation of std::uninitialized_fill_n.
> template<typename _ForwardIterator, typename _Size, typename _Tp>
> _GLIBCXX20_CONSTEXPR
> _ForwardIterator
> __do_uninit_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x)
> {
> _UninitDestroyGuard<_ForwardIterator> __guard(__first);
> - for (; __n > 0; --__n, (void) ++__first)
> +#if __cplusplus >= 201103L
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wc++17-extensions"
> + if constexpr (is_integral<_Size>::value)
> + // Loop will never terminate if __n is negative.
> + __glibcxx_assert(__n >= 0);
> + else if constexpr (is_floating_point<_Size>::value)
> + // Loop will never terminate if __n is not an integer.
> + __glibcxx_assert(__n >= 0 && static_cast<size_t>(__n) == __n);
> +#pragma GCC diagnostic pop
> +#endif
> + for (; __n--; ++__first)
> std::_Construct(std::__addressof(*__first), __x);
> __guard.release();
> return __first;
> }
>
> - template<bool _TrivialValueType>
> +#if __cplusplus < 201103L
> + // Use template specialization for C++98 when 'if constexpr' can't be used.
> + template<bool _CanMemset>
> struct __uninitialized_fill_n
> {
> template<typename _ForwardIterator, typename _Size, typename _Tp>
> @@ -319,47 +477,92 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<>
> struct __uninitialized_fill_n<true>
> {
> + // Overload for generic iterators.
> template<typename _ForwardIterator, typename _Size, typename _Tp>
> static _ForwardIterator
> __uninit_fill_n(_ForwardIterator __first, _Size __n,
> const _Tp& __x)
> - { return std::fill_n(__first, __n, __x); }
> + {
> + if (__unwrappable_niter<_ForwardIterator>::__value)
> + {
> + _ForwardIterator __last = __first;
> + std::advance(__last, __n);
> + __uninitialized_fill<true>::__uninit_fill(__first, __last, __x);
> + return __last;
> + }
> + else
> + return std::__do_uninit_fill_n(__first, __n, __x);
> + }
> };
> -
> +#endif
> /// @endcond
>
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wc++17-extensions"
> // _GLIBCXX_RESOLVE_LIB_DEFECTS
> // DR 1339. uninitialized_fill_n should return the end of its range
> /**
> * @brief Copies the value x into the range [first,first+n).
> - * @param __first An input iterator.
> + * @param __first A forward iterator.
> * @param __n The number of copies to make.
> * @param __x The source value.
> - * @return Nothing.
> + * @return __first + __n.
> *
> - * Like fill_n(), but does not require an initialized output range.
> + * Like std::fill_n, but does not require an initialized output range.
> */
> template<typename _ForwardIterator, typename _Size, typename _Tp>
> inline _ForwardIterator
> uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x)
> {
> + // See uninitialized_fill conditions. We also require _Size to be
> + // an integer. The standard only requires _Size to be decrementable
> + // and contextually convertible to bool, so don't assume first+n works.
> +
> + // FIXME: We could additionally enable this for 1-byte enums.
> +
> typedef typename iterator_traits<_ForwardIterator>::value_type
> _ValueType;
>
> - // Trivial types do not need a constructor to begin their lifetime,
> - // so try to use std::fill_n to benefit from its optimizations.
> - const bool __can_fill
> - = _GLIBCXX_USE_ASSIGN_FOR_INIT(_ValueType, const _Tp&)
> - // For arbitrary class types and floating point types we can't assume
> - // that __n > 0 and std::__size_to_integer(__n) > 0 are equivalent,
> - // so only use std::fill_n when _Size is already an integral type.
> - && __is_integer<_Size>::__value;
> +#if __cplusplus >= 201103L
> + if constexpr (__is_byte<_ValueType>::__value)
> + if constexpr (is_integral<_Tp>::value)
> + if constexpr (is_integral<_Size>::value)
> + {
> + using _BasePtr = decltype(std::__niter_base(__first));
> + if constexpr (is_pointer<_BasePtr>::value)
> + {
> + void* __dest = std::__niter_base(__first);
> + if (__builtin_expect(__n > 0, true))
> + {
> + __builtin_memset(__dest, (unsigned char)__x, __n);
> + __first += __n;
> + }
> + return __first;
> + }
> +#if __cpp_lib_concepts
> + else if constexpr (contiguous_iterator<_ForwardIterator>)
> + {
> + auto __dest = std::__to_address(__first);
> + if (__builtin_expect(__n > 0, true))
> + {
> + __builtin_memset(__dest, (unsigned char)__x, __n);
> + __first += __n;
> + }
> + return __first;
> + }
> +#endif
> + }
> + return std::__do_uninit_fill_n(__first, __n, __x);
> +#else // C++98
> + const bool __can_memset = __is_byte<_ValueType>::__value
> + && __is_integer<_Tp>::__value
> + && __is_integer<_Size>::__value;
>
> - return __uninitialized_fill_n<__can_fill>::
> + return __uninitialized_fill_n<__can_memset>::
> __uninit_fill_n(__first, __n, __x);
> +#endif
> }
> -
> -#undef _GLIBCXX_USE_ASSIGN_FOR_INIT
> +#pragma GCC diagnostic pop
>
> /// @cond undocumented
>
> @@ -619,7 +822,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> = std::__addressof(*__first);
> std::_Construct(__val);
> if (++__first != __last)
> - std::fill(__first, __last, *__val);
> + std::uninitialized_fill(__first, __last, *__val);
> }
> };
>
> @@ -653,7 +856,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> = std::__addressof(*__first);
> std::_Construct(__val);
> ++__first;
> - __first = std::fill_n(__first, __n - 1, *__val);
> + __first = std::uninitialized_fill_n(__first, __n - 1, *__val);
These last two changes seem to be missing in the ChangeLog.
> }
> return __first;
> }
> diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/1.cc
>
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/1.cc
> index 398d8690b56..27b3100d362 100644
> ---
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/1.cc
> +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/1.cc
> @@ -34,4 +34,5 @@ test01(T* result)
> T t[1];
> std::uninitialized_copy(t, t+1, result); // { dg-error "here" }
> }
> -// { dg-error "must be constructible from input type" "" { target *-*-* } 0 }
> +// { dg-error "no matching function" "construct_at" { target c++20 } 0 }
> +// { dg-error "use of deleted function" "T::T(const T&)" { target *-*-* } 0 }
> diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/64476.cc
>
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/64476.cc
> index 2f7dda3417d..e99338dff39 100644
> ---
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/64476.cc
> +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/64476.cc
> @@ -54,8 +54,10 @@ test01()
>
> std::uninitialized_copy(a, a+10, b);
>
> - VERIFY(constructed == 0);
> - VERIFY(assigned == 10);
> + // In GCC 14 and older std::uninitialized_copy was optimized to std::copy
> + // and so used assignments not construction, but that was non-conforming.
> + VERIFY(constructed == 10);
> + VERIFY(assigned == 0);
> }
>
> int
> diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/89164.cc
>
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/89164.cc
> index 48c16da4d32..6e978a7e36c 100644
> ---
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/89164.cc
> +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy/89164.cc
> @@ -35,4 +35,5 @@ void test01()
>
> std::uninitialized_copy(x, x+1, p); // { dg-error "here" }
> }
> -// { dg-error "must be constructible" "" { target *-*-* } 0 }
> +// { dg-error "no matching function" "construct_at" { target c++20 } 0 }
> +// { dg-error "use of deleted function" "X(const X&)" { target *-*-* } 0 }
> diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/89164.cc
>
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/89164.cc
> index 4e8fb0f4af2..96156208372 100644
> ---
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/89164.cc
> +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_copy_n/89164.cc
> @@ -32,4 +32,5 @@ void test01()
>
> std::uninitialized_copy_n(x, 1, p); // { dg-error "here" }
> }
> -// { dg-error "must be constructible" "" { target *-*-* } 0 }
> +// { dg-error "no matching function" "construct_at" { target c++20 } 0 }
> +// { dg-error "use of deleted function" "X(const X&)" { target *-*-* } 0 }
> diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill/89164.cc
>
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill/89164.cc
> index 8353b5882f0..0dcaa1aa9c3 100644
> ---
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill/89164.cc
> +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill/89164.cc
> @@ -32,4 +32,5 @@ void f()
>
> std::uninitialized_fill(p, p+1, x); // { dg-error "here" }
> }
> -// { dg-error "must be constructible" "" { target *-*-* } 0 }
> +// { dg-error "no matching function" "construct_at" { target c++20 } 0 }
> +// { dg-error "use of deleted function" "X(const X&)" { target *-*-* } 0 }
> diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/89164.cc
>
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/89164.cc
> index 4b38c673d32..9b61157b934 100644
> ---
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/89164.cc
> +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/89164.cc
> @@ -32,4 +32,5 @@ void test01()
>
> std::uninitialized_fill_n(p, 1, x); // { dg-error "here" }
> }
> -// { dg-error "must be constructible" "" { target *-*-* } 0 }
> +// { dg-error "no matching function" "construct_at" { target c++20 } 0 }
> +// { dg-error "use of deleted function" "X(const X&)" { target *-*-* } 0 }
> diff --git
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/sizes.cc
>
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/sizes.cc
> index e2ba9355c56..876ec5443fb 100644
> ---
> a/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/sizes.cc
> +++
> b/libstdc++-v3/testsuite/20_util/specialized_algorithms/uninitialized_fill_n/sizes.cc
> @@ -24,21 +24,35 @@ void
> test01()
> {
> int i[4] = { };
> - std::uninitialized_fill_n(i, 2.0001, 0xabcd);
> + // Floating-point n should work, but only if it's an integer value.
> + std::uninitialized_fill_n(i, 3.0, 0xabcd);
> VERIFY( i[0] == 0xabcd );
> VERIFY( i[1] == 0xabcd );
> VERIFY( i[2] == 0xabcd );
> VERIFY( i[3] == 0 );
> }
>
> -// The standard only requires that n>0 and --n are valid expressions.
> +// The standard only requires that `if (n--)` is a valid expression.
> struct Size
> {
> int value;
>
> - void operator--() { --value; }
> + struct testable
> + {
> +#if __cplusplus >= 201103L
> + explicit
> +#endif
> + operator bool() const { return nonzero; }
>
> - int operator>(void*) { return value != 0; }
> + bool nonzero;
> + };
> +
> + testable operator--(int)
> + {
> + testable t = { value != 0 };
> + --value;
> + return t;
> + }
> };
>
> void
> diff --git a/libstdc++-v3/testsuite/23_containers/vector/cons/89164.cc
> b/libstdc++-v3/testsuite/23_containers/vector/cons/89164.cc
> index 106963ecbb9..36907dc508e 100644
> --- a/libstdc++-v3/testsuite/23_containers/vector/cons/89164.cc
> +++ b/libstdc++-v3/testsuite/23_containers/vector/cons/89164.cc
> @@ -32,7 +32,7 @@ void test01()
> X x[1];
> // Should not be able to create vector using uninitialized_copy:
> std::vector<X> v1{x, x+1}; // { dg-error "here" "" { target c++17_down } }
> - // { dg-error "deleted function 'X::X" "" { target c++20 } 0 }
> + // { dg-error "deleted function 'X::X" "" { target *-*-* } 0 }
> }
>
> void test02()
> @@ -41,8 +41,7 @@ void test02()
>
> // Should not be able to create vector using uninitialized_fill_n:
> std::vector<Y> v2{2u, Y{}}; // { dg-error "here" "" { target
> c++17_down } }
> - // { dg-error "deleted function .*Y::Y" "" { target c++20 } 0 }
> + // { dg-error "deleted function .*Y::Y" "" { target *-*-* } 0 }
> }
>
> -// { dg-error "must be constructible from input type" "" { target *-*-* } 0 }
> // { dg-prune-output "construct_at" }
> diff --git a/libstdc++-v3/testsuite/23_containers/vector/cons/89164_c++17.cc
> b/libstdc++-v3/testsuite/23_containers/vector/cons/89164_c++17.cc
> index 09d3dc6f93d..07d4bab9117 100644
> --- a/libstdc++-v3/testsuite/23_containers/vector/cons/89164_c++17.cc
> +++ b/libstdc++-v3/testsuite/23_containers/vector/cons/89164_c++17.cc
> @@ -32,8 +32,7 @@ void test03()
> // Can create initializer_list<Y> with C++17 guaranteed copy elision,
> // but shouldn't be able to copy from it with uninitialized_copy:
> std::vector<X> v3{X{}, X{}, X{}}; // { dg-error "here" "" { target
> c++17_only } }
> - // { dg-error "deleted function .*X::X" "" { target c++20 } 0 }
> + // { dg-error "deleted function .*X::X" "" { target *-*-* } 0 }
> }
>
> -// { dg-error "must be constructible from input type" "" { target *-*-* } 0 }
> // { dg-prune-output "construct_at" }
> --
> 2.46.2
>
>