https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121026

            Bug ID: 121026
           Summary: ranges::uninitialized_xxx algos perform invalid
                    optimizations
           Product: gcc
           Version: 15.1.0
            Status: UNCONFIRMED
          Keywords: wrong-code
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: redi at gcc dot gnu.org
  Target Milestone: ---

Firstly, the _DestroyGuard is a no-op for trivially destructible types:

    template<typename _Iter>
      requires destructible<iter_value_t<_Iter>>
       && is_trivially_destructible_v<iter_value_t<_Iter>>
      struct _DestroyGuard<_Iter>

This partial specialization is a problem, because it means we don't end
lifetimes of objects created during constant evaluation. Prio to C++26 that was
OK, because the guard only has to do anything in the event of an exception,
which wasn't possible in constant expressions. For C++26 we will have constexpr
exceptions, and so should be prepared to run trivial destructors during
constant evaluation.

Maybe we want to just remove that partial specialization, or maybe we want to
keep it but give it a destructor (for C++26 only?) that only destroys objects
if ! consteval.


Secondly, ranges::uninitialized_default_construct is a no-op for trivially
default constructible types:

       if constexpr (is_trivially_default_constructible_v<_ValueType>)
         return ranges::next(__first, __last);

That's not correct for C++26 where the algo can be used in constant
expressions. We need to begin object lifetimes for consteval. Something like:

        if constexpr (is_trivially_default_constructible_v<_ValueType>)
#if __glibcxx_raw_memory_algorithms >= 202411L
          if ! consteval
#endif
          { return ranges::next(__first, __last); }

(N.B. that feature test macro isn't defined for Clang 19 because it doesn't
support constexpr placement new, P2747R2, but that means it can't support
constexpr uninitialized_default_construct anyway).


Thirdly, many of the other ranges::uninitialized_xxx algos reproduce similar
optimizations that the std::uninitialized_xxx algos used to do, e.g.
ranges::uninitialized_copy will use ranges::copy for trivially constructible
and trivially assignable types, and ranges::uninitialized_value_construct will
use ranges::fill. That is also incorrect for constexpr, because we need to
begin object lifetimes.

Also, for the non-ranges algos, the logic was rewritten for Bug 68350. We might
want to audit the ranges algos to see if similar changes are needed/wanted.


Fourthly, I don't think this ranges::uninitialized_move optimization is valid:

            auto [__in, __out]
              = ranges::copy_n(std::make_move_iterator(std::move(__ifirst)),
                               __d, __ofirst);

Not only does this fail to begin object lifetimes during constant evaluation
(as noted above), but std::move_iterator will move the values using std::move.
If the type has an overloaded iter_move findable via ADL then we should be
using that instead of std::move.

I think we can only optimize ranges::uninitialized_move for arithmetic types
(and pointers to arithmetic types?). That would enable the use of memcpy for
those types, so still seems worthwhile.

Reply via email to