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
> 
> 

Reply via email to