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

Reply via email to