On Thu, 17 Oct 2024 at 02:39, Patrick Palka <ppa...@redhat.com> wrote:
>
> 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 ...

I don't think the move_iterator overload is needed here.

We should never have a debug mode _Safe_iterator that wraps a move_iterator.

>
> > +    __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?

But here, maybe yes.

I'll see if I can come up with a test that fails, to verify that
moving it helps.

I think the wrapping cases we need to handle are:

T* __normal_iterator _Safe_iterator? reverse_iterator? move_iterator?

Where the later names wrap the earlier ones, and ? means it might not
be present.

So we want to support
move_iterator<reverse_iterator<_Safe_iterator<__normal_iterator<T*>>>>
but we don't need to care about e.g.

__normal_iterator<reverse_iterator<move_iterator<T*>>>

Although it's possible that a user could create a reverse_iterator
that wraps a move_iterator, we don't create those in the library
itself, so if it's not optimized down to a pointer, that's OK.




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