libstdc++: Ensure that ranges::destroy destroys in constexpr [PR121024]

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.

Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
This commit is contained in:
Jonathan Wakely 2025-07-10 14:12:44 +01:00 committed by Jonathan Wakely
parent aeeeeef396
commit a63b663c7b
No known key found for this signature in database
2 changed files with 89 additions and 14 deletions

View File

@ -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;
}
};

View File

@ -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>() );