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.