[gcc r16-2256] libstdc++: Ensure that ranges::destroy destroys in constexpr [PR121024]
Jonathan Wakely
redi@gcc.gnu.org
Tue Jul 15 09:40:10 GMT 2025
https://gcc.gnu.org/g:a63b663c7bf77e38d90cebe993a3de8a548f8d66
commit r16-2256-ga63b663c7bf77e38d90cebe993a3de8a548f8d66
Author: Jonathan Wakely <jwakely@redhat.com>
Date: Thu Jul 10 14:12:44 2025 +0100
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>
Diff:
---
libstdc++-v3/include/bits/ranges_uninitialized.h | 26 ++++----
.../specialized_algorithms/destroy/121024.cc | 77 ++++++++++++++++++++++
2 files changed, 89 insertions(+), 14 deletions(-)
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>() );
More information about the Libstdc++-cvs
mailing list