]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Ensure that ranges::destroy destroys in constexpr [PR121024]
authorJonathan Wakely <jwakely@redhat.com>
Thu, 10 Jul 2025 13:12:44 +0000 (14:12 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Tue, 15 Jul 2025 09:25:48 +0000 (10:25 +0100)
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>
libstdc++-v3/include/bits/ranges_uninitialized.h
libstdc++-v3/testsuite/20_util/specialized_algorithms/destroy/121024.cc [new file with mode: 0644]

index 12a714b68aa0743f37507ee45851c4f17fd90068..3f9a07fdf5c61e17de883a8756455e2f388276df 100644 (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;
       }
   };
 
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 (file)
index 0000000..781dd40
--- /dev/null
@@ -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>() );