__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>
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;
}
};
--- /dev/null
+// { 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>() );