[PATCH] c++, libstdc++, v3: Implement C++26 P3726R2 - Adjustments to Union Lifetime Rules [PR125845]

Jakub Jelinek jakub@redhat.com
Wed Sep 9 09:26:46 GMT 2026


On Wed, Sep 09, 2026 at 08:42:32AM +0200, Tomasz Kaminski wrote:
> I would much more preffer the start_lifetime(a.x) to be non-constant
> expressions
> if `a` (or any containing object) is not within its lifetime.
> 
> I believe this function should start a lifetime of at most a single object.
> Otherwise
> it would be unclear, if the enclosing objects should be created (with all
> members)
> or only have their lifetimes started. This becomes even more complicated if
> we
> consider non-trivial enclosing objects.
> 
> Also, I want to avoid orphaned in-lifetime objects, when we have some
> subobject
> in lifetime, while some of the containing objects are not. This is why the
> paper hasn't
> proposed end_lifetime function, as it would naturally lead to such
> situations, and
> instead relies on storage reuse to end lieftime of any objects that have
> they lifetime started.

Here is an incremental patch on top of the patch posted today, which
essentially performs a __builtin_is_within_lifetime call on the containing
(sub)object if any before trying to evaluate the store.

--- a/gcc/cp/constexpr.cc	2026-09-08 22:26:48.000000000 +0200
+++ b/gcc/cp/constexpr.cc	2026-09-09 10:47:26.825464921 +0200
@@ -2541,62 +2541,13 @@ cxx_eval_constexpr_diag (const constexpr
 static tree eval_and_check_array_index (const constexpr_ctx *, tree, bool,
 					bool *, bool *, tree *);
 
-/* Attempt to evaluate T which represents a call to
-   __builtin_is_within_lifetime.  */
+/* Helper function for cxx_eval_is_within_lifetime and
+   cxx_eval_start_lifetime.  Check if ARG is within lifetime.  */
 
 static tree
-cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t,
-			     bool *non_constant_p, bool *overflow_p,
-			     tree *jump_target)
+is_within_lifetime (location_t loc, const constexpr_ctx *ctx, tree t, tree arg,
+		    bool *non_constant_p, bool *overflow_p, tree *jump_target)
 {
-  location_t loc = EXPR_LOCATION (t);
-  tree arg = CALL_EXPR_ARG (t, 0);
-  arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
-				      non_constant_p, overflow_p,
-				      jump_target);
-  if (*jump_target)
-    return NULL_TREE;
-  if (*non_constant_p)
-    return t;
-  /* Need to strip casts to pointers to void, but should preserve
-     pointer casts within the innermost pointer to void cast if
-     any, so that e.g. pointers to heap allocations are handled
-     correctly.  */
-  tree p = arg;
-  while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR)
-    {
-      if (!TYPE_PTR_P (TREE_TYPE (p)))
-	break;
-      if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p))))
-	arg = TREE_OPERAND (p, 0);
-      p = TREE_OPERAND (p, 0);
-    }
-  if (integer_zerop (arg))
-    {
-      if (!ctx->quiet)
-	error_at (loc, "%qs called with a null pointer",
-		  "__builtin_is_within_lifetime");
-      *non_constant_p = true;
-      return t;
-    }
-  if (POINTER_TYPE_P (TREE_TYPE (arg))
-      && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE
-	  || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE))
-    {
-      if (!ctx->quiet)
-	error_at (loc, "%qs called with pointer to function",
-		  "__builtin_is_within_lifetime");
-      *non_constant_p = true;
-      return t;
-    }
-  arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
-  arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
-				      non_constant_p, overflow_p,
-				      jump_target);
-  if (*jump_target)
-    return NULL_TREE;
-  if (*non_constant_p)
-    return t;
   auto_vec <tree, 4> refs;
   tree obj;
   for (obj = arg; ; obj = TREE_OPERAND (obj, 0))
@@ -2793,6 +2744,66 @@ cxx_eval_is_within_lifetime (const const
   return boolean_true_node;
 }
 
+/* Attempt to evaluate T which represents a call to
+   __builtin_is_within_lifetime.  */
+
+static tree
+cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t,
+			     bool *non_constant_p, bool *overflow_p,
+			     tree *jump_target)
+{
+  location_t loc = EXPR_LOCATION (t);
+  tree arg = CALL_EXPR_ARG (t, 0);
+  arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
+				      non_constant_p, overflow_p,
+				      jump_target);
+  if (*jump_target)
+    return NULL_TREE;
+  if (*non_constant_p)
+    return t;
+  /* Need to strip casts to pointers to void, but should preserve
+     pointer casts within the innermost pointer to void cast if
+     any, so that e.g. pointers to heap allocations are handled
+     correctly.  */
+  tree p = arg;
+  while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR)
+    {
+      if (!TYPE_PTR_P (TREE_TYPE (p)))
+	break;
+      if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p))))
+	arg = TREE_OPERAND (p, 0);
+      p = TREE_OPERAND (p, 0);
+    }
+  if (integer_zerop (arg))
+    {
+      if (!ctx->quiet)
+	error_at (loc, "%qs called with a null pointer",
+		  "__builtin_is_within_lifetime");
+      *non_constant_p = true;
+      return t;
+    }
+  if (POINTER_TYPE_P (TREE_TYPE (arg))
+      && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE
+	  || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE))
+    {
+      if (!ctx->quiet)
+	error_at (loc, "%qs called with pointer to function",
+		  "__builtin_is_within_lifetime");
+      *non_constant_p = true;
+      return t;
+    }
+  arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
+  arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
+				      non_constant_p, overflow_p,
+				      jump_target);
+  if (*jump_target)
+    return NULL_TREE;
+  if (*non_constant_p)
+    return t;
+  return is_within_lifetime (loc, ctx, t, arg, non_constant_p, overflow_p,
+			     jump_target);
+}
+
 static tree cxx_eval_store_expression (const constexpr_ctx *, tree,
 				       value_cat, bool *, bool *,
 				       tree *);
@@ -2805,7 +2816,14 @@ cxx_eval_start_lifetime (const constexpr
 			 bool *overflow_p, tree *jump_target)
 {
   location_t loc = EXPR_LOCATION (t);
-  tree arg = CALL_EXPR_ARG (t, 0);
+  tree arg = CALL_EXPR_ARG (t, 0), within_lifetime;
+  arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
+				      non_constant_p, overflow_p,
+				      jump_target);
+  if (*jump_target)
+    return NULL_TREE;
+  if (*non_constant_p)
+    return t;
   arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
   arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
 				      non_constant_p, overflow_p,
@@ -2814,6 +2832,31 @@ cxx_eval_start_lifetime (const constexpr
     return NULL_TREE;
   if (*non_constant_p)
     return t;
+  switch (TREE_CODE (arg))
+    {
+    case COMPONENT_REF:
+    case ARRAY_REF:
+    case REALPART_EXPR:
+    case IMAGPART_EXPR:
+      within_lifetime
+	= is_within_lifetime (loc, ctx, t, TREE_OPERAND (arg, 0),
+			      non_constant_p, overflow_p, jump_target);
+      if (*jump_target)
+	return NULL_TREE;
+      if (*non_constant_p)
+	return t;
+      if (within_lifetime != boolean_true_node)
+	{
+	  if (!ctx->quiet)
+	    error_at (loc, "%qs with containing object not within lifetime",
+		      "__builtin_is_within_lifetime");
+	  *non_constant_p = true;
+	  return t;
+	}
+      break;
+    default:
+      break;
+    }
   tree ctor = build_constructor (TREE_TYPE (arg), NULL);
   CONSTRUCTOR_NO_CLEARING (ctor) = 1;
   CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1;
--- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C	2026-09-08 22:19:17.810884355 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C	2026-09-09 11:15:08.446464272 +0200
@@ -76,12 +76,92 @@ constexpr E
 fE3 (bool x)
 {
   E a;
-  __builtin_start_lifetime (&a.a.b[0]);
+  __builtin_start_lifetime (&a.a.b[0]);	// { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
   if (x)
     __builtin_start_lifetime (&a.a.b[1]);
   a.a.b[0][0] = 1;
   a.a.b[1][1] = 3;
   return a;
 }
-constexpr E e4 = fE3 (false);		// { dg-error "is not a constant expression" }
-constexpr E e5 = fE3 (true);		// { dg-error "is not a constant expression" "" { target c++23_down } }
+constexpr E e4 = fE3 (false);
+constexpr E e5 = fE3 (true);
+union H { int a; A b[2][2]; };
+union I { int a; H b[2]; };
+constexpr bool
+fH (int x)
+{
+  G a;
+  I b;
+  switch (x)
+    {
+    case 0:
+      __builtin_start_lifetime (&a.b);
+      break;
+    case 1:
+      __builtin_start_lifetime (&a.b[0]);	// { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    case 2:
+      a.a = 42;
+      __builtin_start_lifetime (&a.b);
+      break;
+    case 3:
+      a.a = 42;
+      __builtin_start_lifetime (&a.b[1]);	// { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    case 4:
+      __builtin_start_lifetime (&b.b);
+      break;
+    case 5:
+      __builtin_start_lifetime (&b.b[0]);	// { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    case 6:
+      a.a = 42;
+      __builtin_start_lifetime (&b.b);
+      break;
+    case 7:
+      a.a = 42;
+      __builtin_start_lifetime (&b.b[1]);	// { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    case 8:
+      __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    case 9:
+      __builtin_start_lifetime (&b.b);
+      __builtin_start_lifetime (&b.b[1]);
+      __builtin_start_lifetime (&b.b[1].b);
+      __builtin_start_lifetime (&b.b[1].b[0]);
+      __builtin_start_lifetime (&b.b[1].b[0][1]);
+      break;
+    case 10:
+      __builtin_start_lifetime (&b.b);
+      __builtin_start_lifetime (&b.b[1]);
+      __builtin_start_lifetime (&b.b[1].b);
+      __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    case 11:
+      __builtin_start_lifetime (&b.b);
+      __builtin_start_lifetime (&b.b[1]);
+      __builtin_start_lifetime (&b.b[1].b[0]); // { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    case 12:
+      __builtin_start_lifetime (&b.b);
+      __builtin_start_lifetime (&b.b[1].b); // { dg-error "'__builtin_is_within_lifetime' with containing object not within lifetime" }
+      break;
+    default:
+      break;
+    }
+  return true;
+}
+static_assert (fH (0));
+constexpr bool h1 = fH (1);
+static_assert (fH (2));
+constexpr bool h3 = fH (3);
+static_assert (fH (4));
+constexpr bool h5 = fH (5);
+static_assert (fH (6));
+constexpr bool h7 = fH (7);
+constexpr bool h8 = fH (8);
+static_assert (fH (9));
+constexpr bool h10 = fH (10);
+constexpr bool h11 = fH (11);
+constexpr bool h12 = fH (12);
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C	2026-09-08 22:19:17.811118455 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C	2026-09-09 10:53:15.851010501 +0200
@@ -59,6 +59,7 @@ foo ()
   if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
     return __LINE__;
   std::start_lifetime (l.j);
+  std::start_lifetime (l.j.i);
   std::start_lifetime (l.j.i[1]);
   if (!std::is_within_lifetime (&l.j.i[1]))
     return __LINE__;


	Jakub



More information about the Libstdc++ mailing list