On Mon, Jul 14, 2025 at 10:41 PM Jonathan Wakely <jwak...@redhat.com> wrote:
> The new test is currently marked as XFAIL because PR c++/102284 means > that GCC doesn't notice that the lifetimes have ended. > > libstdc++-v3/ChangeLog: > > PR libstdc++/121024 > * include/bits/ranges_uninitialized.h (ranges::destroy): Do not > optimize away trivial destructors during constant evaluation. > (ranges::destroy_n): Likewise. > * testsuite/20_util/specialized_algorithms/destroy/121024.cc: > New test. > --- > > Tested powerpc64le-linux. > > GCC doesn't care about this change, but it makes Clang happy. > LGTM. I want this merged, as it may impact inplace_vector. > > .../include/bits/ranges_uninitialized.h | 26 +++---- > .../specialized_algorithms/destroy/121024.cc | 77 +++++++++++++++++++ > 2 files changed, 89 insertions(+), 14 deletions(-) > create mode 100644 > libstdc++-v3/testsuite/20_util/specialized_algorithms/destroy/121024.cc > > diff --git a/libstdc++-v3/include/bits/ranges_uninitialized.h > b/libstdc++-v3/include/bits/ranges_uninitialized.h > index 12a714b68aa0..3f9a07fdf5c6 100644 > --- a/libstdc++-v3/include/bits/ranges_uninitialized.h > +++ b/libstdc++-v3/include/bits/ranges_uninitialized.h > @@ -556,13 +556,12 @@ namespace ranges > __destroy_fn::operator()(_Iter __first, _Sent __last) const noexcept > { > if constexpr (is_trivially_destructible_v<iter_value_t<_Iter>>) > - return ranges::next(std::move(__first), __last); > - else > - { > - for (; __first != __last; ++__first) > - ranges::destroy_at(std::__addressof(*__first)); > - return __first; > - } > + if (!is_constant_evaluated()) > + return ranges::next(std::move(__first), __last); > + > + for (; __first != __last; ++__first) > + ranges::destroy_at(std::__addressof(*__first)); > + return __first; > } > > template<__detail::__nothrow_input_range _Range> > @@ -581,13 +580,12 @@ namespace ranges > operator()(_Iter __first, iter_difference_t<_Iter> __n) const > noexcept > { > if constexpr (is_trivially_destructible_v<iter_value_t<_Iter>>) > - return ranges::next(std::move(__first), __n); > - else > - { > - for (; __n > 0; ++__first, (void)--__n) > - ranges::destroy_at(std::__addressof(*__first)); > - return __first; > - } > + if (!is_constant_evaluated()) > + return ranges::next(std::move(__first), __n); > + > + for (; __n > 0; ++__first, (void)--__n) > + ranges::destroy_at(std::__addressof(*__first)); > + return __first; > } > }; > > diff --git > a/libstdc++-v3/testsuite/20_util/specialized_algorithms/destroy/121024.cc > b/libstdc++-v3/testsuite/20_util/specialized_algorithms/destroy/121024.cc > new file mode 100644 > index 000000000000..781dd404750c > --- /dev/null > +++ > b/libstdc++-v3/testsuite/20_util/specialized_algorithms/destroy/121024.cc > @@ -0,0 +1,77 @@ > +// { dg-do compile { target c++26 } } > + > +// Bug 121024 > +// ranges::destroy and ranges::destroy_n do not end lifetime of trivial > types > + > +#include <memory> > + > +consteval bool is_within_lifetime(const auto* p) noexcept > +{ > + return __builtin_constant_p(*p); > +} > + > +template<typename T> > +struct Buf > +{ > + constexpr Buf() : p(std::allocator<T>().allocate(2)) { } > + constexpr ~Buf() { std::allocator<T>().deallocate(p, 2); } > + T* p; > +}; > + > +template<typename T> > +consteval bool > +test_destroy() > +{ > + Buf<T> buf; > + std::uninitialized_value_construct(buf.p, buf.p + 2); > + std::destroy(buf.p, buf.p + 2); > + return not is_within_lifetime(buf.p) && not is_within_lifetime(buf.p + > 1); > +} > + > +template<typename T> > +consteval bool > +test_destroy_n() > +{ > + Buf<T> buf; > + std::uninitialized_value_construct_n(buf.p, 2); > + std::destroy_n(buf.p, 2); > + return not is_within_lifetime(buf.p) && not is_within_lifetime(buf.p + > 1); > +} > + > +template<typename T> > +consteval bool > +test_ranges_destroy() > +{ > + Buf<T> buf; > + std::uninitialized_value_construct(buf.p, buf.p + 2); > + std::ranges::destroy(buf.p, buf.p + 2); > + return not is_within_lifetime(buf.p) && not is_within_lifetime(buf.p + > 1); > +} > + > +template<typename T> > +consteval bool > +test_ranges_destroy_n() > +{ > + Buf<T> buf; > + std::uninitialized_value_construct_n(buf.p, 2); > + std::ranges::destroy_n(buf.p, 2); > + return not is_within_lifetime(buf.p) && not is_within_lifetime(buf.p + > 1); > +} > + > +struct O > +{ > + constexpr O() { } > + constexpr ~O() { } > +}; > + > +// These all fail for GCC because is_within_lifetime still returns true > +// after the lifetime has been ended. > +// { dg-xfail-if "PR c++/102284" { *-*-* } } > +static_assert( test_destroy<int>() ); > +static_assert( test_destroy<O>() ); > +static_assert( test_destroy_n<int>() ); > +static_assert( test_destroy_n<O>() ); > +static_assert( test_ranges_destroy<int>() ); > +static_assert( test_ranges_destroy<O>() ); > +static_assert( test_ranges_destroy_n<int>() ); > +static_assert( test_ranges_destroy_n<O>() ); > -- > 2.50.1 > >