Hi!

Is my understanding correct that for stores changing the active union
member at constant evaluation time we want to start lifetime of all
subobjects except variant members, while std::start_lifetime only does
that without any subobjects?

Furthermore, there are the https://wg21.link/P2641R4 and
https://wg21.link/P3450R1 std::is_within_lifetime papers which we need to
implement and track lifetime even in more detail.  Looking at clang, they
do implement the first paper using __builtin_is_within_lifetime builtin, but
not the second one; wonder if the second paper is implementable purely in
headers (somehow try to evaluate if static_cast<const volatile U*>(p)
is a constant expression) or whether we need a two argument builtin instead of 
one
(where the other argument would be say a null pointer of the
const volatile U * type) and how to deal with compatibility when LLVM 22
already has __has_builtin(__builtin_is_within_lifetime) but not
support the 2 argument one.

Anyway, my limited understanding is that lifetime of an object starts at the
end of a constructor or during vacuous initialization and ends when
destructor starts, the object is destroyed or reused.

So, I think solely for __builtin_start_lifetime purposes we need to
differentiate between CONSTRUCTOR_NO_CLEARING CONSTRUCTORs which do have
all omitted elements within lifetime and such constructors which have all
omitted elements not within lifetime, but then also need a flag on each
CONSTRUCTOR whether that whole subobject is within lifetime.
Does e.g. ptr[2].~T (); on an array previously all within lifetime make
that particular element not within a lifetime?  Or only for class types?
The hole checking in the patch then would need to differentiate between the
within lifetime and not within lifetime holes.

Attaching just for tracking purposes the extracted uncommitted part of the
trivial union patchset (rest has been committed in r17-671), so far unmodified
further.

2026-05-21  Jakub Jelinek  <[email protected]>

gcc/c-family/
        * c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine
        __cpp_trivial_union to 202603L.
gcc/cp/
        * cp-tree.h: Implement C++26 P3726R2 - Adjustments to Union Lifetime
        Rules.
        (enum cp_built_in_function): Add CP_BUILT_IN_START_LIFETIME.
        (check_builtin_start_lifetime): Declare.
        (reduced_constant_expression_p): Add a bool argument defaulted to
        false.
        * tree.cc (builtin_valid_in_constant_expr_p): Handle
        CP_BUILT_IN_START_LIFETIME.
        * decl.cc (cxx_init_decl_processing): Create __builtin_start_lifetime
        decl.
        * cp-gimplify.cc (cp_gimplify_expr): Handle
        CP_BUILT_IN_START_LIFETIME.
        * semantics.cc (check_builtin_start_lifetime): New function.
        * constexpr.cc (cxx_eval_start_lifetime): New function.
        (cxx_eval_builtin_function_call): Handle CP_BUILT_IN_START_LIFETIME.
        (reduced_constant_expression_p): Add union_elemental_subobject
        argument and propagate it to recursive calls.  If set, ignore holes
        in array initializers.
        (cxx_eval_store_expression): Add start_lifetime argument.  Don't
        preevaluate init in tha tcase, return early if !seen_union after
        loop around the possibly nested ARRAY_REFs/COMPONENT_REFs etc.
        or when all union members are already active.
gcc/testsuite/
        * g++.dg/cpp26/feat-cxx26.C: Adjust __cpp_trivial_union checking.
        * g++.dg/cpp26/trivial-union3.C: New test.
        * g++.dg/cpp26/trivial-union4.C: New test.
        * g++.dg/cpp26/trivial-union5.C: New test.
        * g++.dg/cpp26/trivial-union6.C: New test.
libstdc++-v3/
        * include/bits/version.def (start_lifetime): New.
        * include/bits/version.h: Regenerate.
        * include/bits/stl_construct.h (std::start_lifetime): New function
        template.
        * include/std/memory: Define __glibcxx_want_start_lifetime before
        including bits/version.h.
        * src/c++23/std.cc.in: Add export std::start_lifetime.

--- gcc/c-family/c-cppbuiltin.cc.jj     2026-05-20 08:43:02.545478650 +0200
+++ gcc/c-family/c-cppbuiltin.cc        2026-05-20 14:26:01.233439033 +0200
@@ -1122,7 +1122,7 @@ c_cpp_builtins (cpp_reader *pfile)
            cpp_define (pfile, "__cpp_impl_reflection=202603L");
          else
            cpp_warn (pfile, "__cpp_impl_reflection");
-         cpp_define (pfile, "__cpp_trivial_union=202502L");
+         cpp_define (pfile, "__cpp_trivial_union=202603L");
        }
       if (flag_concepts && cxx_dialect > cxx14)
        cpp_define (pfile, "__cpp_concepts=202002L");
--- gcc/cp/cp-tree.h.jj 2026-05-20 08:43:07.245399375 +0200
+++ gcc/cp/cp-tree.h    2026-05-20 14:26:01.237438967 +0200
@@ -7245,6 +7245,7 @@ enum cp_built_in_function {
   CP_BUILT_IN_CONSTEXPR_DIAG,
   CP_BUILT_IN_CURRENT_EXCEPTION,
   CP_BUILT_IN_UNCAUGHT_EXCEPTIONS,
+  CP_BUILT_IN_START_LIFETIME,
   CP_BUILT_IN_LAST
 };
 
@@ -8666,6 +8667,7 @@ extern tree fold_builtin_is_correspondin
 extern bool pointer_interconvertible_base_of_p (tree, tree, bool = false);
 extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, 
int, tree *);
 extern tree fold_builtin_is_string_literal     (location_t, int, tree *);
+extern bool check_builtin_start_lifetime       (location_t, int, tree *, 
tsubst_flags_t);
 extern tree finish_structured_binding_size     (location_t, tree, 
tsubst_flags_t);
 extern tree finish_trait_expr                  (location_t, enum 
cp_trait_kind, tree, tree);
 extern tree finish_trait_type                  (enum cp_trait_kind, tree, 
tree, tsubst_flags_t);
@@ -9435,7 +9437,7 @@ extern tree fold_non_dependent_init               (tr
                                                 bool = false, tree = 
NULL_TREE);
 extern tree fold_simple                                (tree);
 extern tree fold_to_constant                   (tree);
-extern bool reduced_constant_expression_p       (tree, tree = NULL_TREE);
+extern bool reduced_constant_expression_p       (tree, tree = NULL_TREE, bool 
= false);
 extern bool is_instantiation_of_constexpr       (tree);
 extern bool var_in_constexpr_fn                 (tree);
 extern bool var_in_maybe_constexpr_fn           (tree);
--- gcc/cp/tree.cc.jj   2026-05-20 08:43:02.572478195 +0200
+++ gcc/cp/tree.cc      2026-05-20 14:26:01.238177470 +0200
@@ -574,6 +574,7 @@ builtin_valid_in_constant_expr_p (const_
          case CP_BUILT_IN_CONSTEXPR_DIAG:
          case CP_BUILT_IN_CURRENT_EXCEPTION:
          case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS:
+         case CP_BUILT_IN_START_LIFETIME:
            return true;
          default:
            break;
--- gcc/cp/decl.cc.jj   2026-05-20 08:43:02.564478330 +0200
+++ gcc/cp/decl.cc      2026-05-20 14:26:01.240438917 +0200
@@ -5663,6 +5663,13 @@ cxx_init_decl_processing (void)
                               BUILT_IN_FRONTEND, NULL, NULL_TREE);
   set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
 
+  tree void_vaftype = build_varargs_function_type_list (boolean_type_node,
+                                                       NULL_TREE);
+  decl = add_builtin_function ("__builtin_start_lifetime",
+                              void_vaftype, CP_BUILT_IN_START_LIFETIME,
+                              BUILT_IN_FRONTEND, NULL, NULL_TREE);
+  set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
+
   integer_two_node = build_int_cst (NULL_TREE, 2);
 
   /* Guess at the initial static decls size.  */
--- gcc/cp/cp-gimplify.cc.jj    2026-05-20 08:43:02.554478498 +0200
+++ gcc/cp/cp-gimplify.cc       2026-05-20 14:26:01.241438900 +0200
@@ -1015,6 +1015,14 @@ cp_gimplify_expr (tree *expr_p, gimple_s
              case CP_BUILT_IN_CONSTEXPR_DIAG:
                *expr_p = void_node;
                break;
+             case CP_BUILT_IN_START_LIFETIME:
+               check_builtin_start_lifetime (EXPR_LOCATION (*expr_p),
+                                             call_expr_nargs (*expr_p),
+                                             call_expr_nargs (*expr_p)
+                                             ? &CALL_EXPR_ARG (*expr_p, 0)
+                                             : NULL,
+                                             tf_warning_or_error);
+               *expr_p = void_node;
              default:
                break;
              }
--- gcc/cp/semantics.cc.jj      2026-05-20 08:43:07.247399342 +0200
+++ gcc/cp/semantics.cc 2026-05-20 14:26:01.242438884 +0200
@@ -13989,6 +13989,53 @@ fold_builtin_is_string_literal (location
   return boolean_true_node;
 }
 
+/* Perform __builtin_start_lifetime call checking, return false
+   when errors are reported.  */
+
+bool
+check_builtin_start_lifetime (location_t loc, int nargs, tree *args,
+                             tsubst_flags_t complain)
+{
+  /* Unless users call the builtin directly, the following 2 checks should be
+     ensured from std::start_lifetime template.  */
+  if (nargs != 1)
+    {
+      if (complain & tf_error)
+       error_at (loc, "%<__builtin_start_lifetime%> needs a single "
+                 "argument");
+      return false;
+    }
+  tree arg = args[0];
+  if (error_operand_p (arg))
+    return false;
+  tree ptype = TREE_TYPE (arg);
+  if (!POINTER_TYPE_P (ptype))
+    {
+      if (complain & tf_error)
+       error_at (loc, "%<__builtin_start_lifetime%> argument type %qT "
+                 "is not pointer type", ptype);
+      return false;
+    }
+  tree type = TREE_TYPE (ptype);
+  if (!complete_type_or_else (type, NULL_TREE))
+    return false;
+  if (!CP_AGGREGATE_TYPE_P (type))
+    {
+      if (complain & tf_error)
+       error_at (loc, "%<__builtin_start_lifetime%> argument type %qT "
+                 "is not a pointer to aggregate type", ptype);
+      return false;
+    }
+  if (!implicit_lifetime_type_p (type))
+    {
+      if (complain & tf_error)
+       error_at (loc, "%<__builtin_start_lifetime%> argument type %qT "
+                 "is not a pointer to implicit-lifetime type", ptype);
+      return false;
+    }
+  return true;
+}
+
 /* [basic.types] 8.  True iff TYPE is an object type.  */
 
 static bool
--- gcc/cp/constexpr.cc.jj      2026-05-20 08:43:07.244399392 +0200
+++ gcc/cp/constexpr.cc 2026-05-20 14:26:01.243438867 +0200
@@ -2456,6 +2456,49 @@ cxx_eval_constexpr_diag (const constexpr
   return void_node;
 }
 
+static tree cxx_eval_store_expression (const constexpr_ctx *, tree,
+                                      value_cat, bool *, bool *,
+                                      tree *, bool = false);
+
+/* Attempt to evaluate T which represents a call to
+   __builtin_start_lifetime.  */
+
+static tree
+cxx_eval_start_lifetime (const constexpr_ctx *ctx, tree t, bool 
*non_constant_p,
+                        bool *overflow_p, tree *jump_target)
+{
+  location_t loc = EXPR_LOCATION (t);
+  if (!check_builtin_start_lifetime (loc, call_expr_nargs (t),
+                                    call_expr_nargs (t)
+                                    ? &CALL_EXPR_ARG (t, 0) : NULL,
+                                    ctx->quiet ? tf_none
+                                    : tf_warning_or_error))
+    {
+      *non_constant_p = true;
+      return t;
+    }
+  tree arg = CALL_EXPR_ARG (t, 0);
+  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;
+  tree ctor = build_constructor (TREE_TYPE (arg), NULL);
+  CONSTRUCTOR_NO_CLEARING (ctor) = 1;
+  tree modexpr = build2 (MODIFY_EXPR, TREE_TYPE (arg), arg, ctor);
+  arg = cxx_eval_store_expression (ctx, modexpr, vc_discard, non_constant_p,
+                                  overflow_p, jump_target, true);
+  ggc_free (modexpr);
+  if (*jump_target)
+    return NULL_TREE;
+  if (*non_constant_p)
+    return t;
+  return void_node;
+}
+
 /* Attempt to evaluate T which represents a call to a builtin function.
    We assume here that all builtin functions evaluate to scalar types
    represented by _CST nodes.  */
@@ -2529,6 +2572,10 @@ cxx_eval_builtin_function_call (const co
        return cxx_eval_constexpr_diag (ctx, t, non_constant_p, overflow_p,
                                        jump_target);
 
+      case CP_BUILT_IN_START_LIFETIME:
+        return cxx_eval_start_lifetime (ctx, t, non_constant_p, overflow_p,
+                                       jump_target);
+
       default:
        break;
       }
@@ -4708,7 +4755,8 @@ cxx_eval_call_expression (const constexp
    FIXME speed this up, it's taking 16% of compile time on sieve testcase.  */
 
 bool
-reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */)
+reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */,
+                              bool union_elemental_subobject /* = false */)
 {
   if (t == NULL_TREE)
     return false;
@@ -4732,7 +4780,10 @@ reduced_constant_expression_p (tree t, t
        return false;
       if (CONSTRUCTOR_NO_CLEARING (t))
        {
-         if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE)
+         if (union_elemental_subobject
+             && TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE)
+           field = NULL_TREE;
+         else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE)
            {
              /* There must be a valid constant initializer at every array
                 index.  */
@@ -4766,12 +4817,25 @@ reduced_constant_expression_p (tree t, t
                 active member has no constituent values, so all constituent
                 values are constant.  */
              field = NULL_TREE;
+             union_elemental_subobject = cxx_dialect >= cxx26;
            }
          else
-           field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t)));
+           {
+             field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t)));
+             union_elemental_subobject = false;
+           }
        }
       else
-       field = NULL_TREE;
+       {
+         field = NULL_TREE;
+         if (cxx_dialect >= cxx26)
+           {
+             if (TREE_CODE (TREE_TYPE (t)) == UNION_TYPE)
+               union_elemental_subobject = true;
+             else if (TREE_CODE (TREE_TYPE (t)) != ARRAY_TYPE)
+               union_elemental_subobject = false;
+           }
+       }
       for (auto &e: CONSTRUCTOR_ELTS (t))
        {
          /* If VAL is null, we're in the middle of initializing this
@@ -4781,7 +4845,8 @@ reduced_constant_expression_p (tree t, t
                                               && (TREE_CODE (e.index)
                                                   == FIELD_DECL))
                                              ? DECL_SIZE (e.index)
-                                             : NULL_TREE))
+                                             : NULL_TREE,
+                                             union_elemental_subobject))
            return false;
          /* We want to remove initializers for empty fields in a struct to
             avoid confusing output_constructor.  */
@@ -7817,7 +7882,7 @@ static tree
 cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
                           value_cat lval,
                           bool *non_constant_p, bool *overflow_p,
-                          tree *jump_target)
+                          tree *jump_target, bool start_lifetime/*=false*/)
 {
   constexpr_ctx new_ctx = *ctx;
 
@@ -7830,7 +7895,7 @@ cxx_eval_store_expression (const constex
 
   tree type = TREE_TYPE (target);
   bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR;
-  if (preeval && !TREE_CLOBBER_P (init))
+  if (preeval && !TREE_CLOBBER_P (init) && !start_lifetime)
     {
       /* Ignore var = .DEFERRED_INIT (); for now, until PR121965 is fixed.  */
       if (flag_auto_var_init > AUTO_INIT_UNINITIALIZED
@@ -8037,6 +8102,11 @@ cxx_eval_store_expression (const constex
       const_object_being_modified = NULL_TREE;
     }
 
+  /* For __builtin_start_lifetime, if no union is involved, no need to
+     change anything.  */
+  if (start_lifetime && !seen_union)
+    return void_node;
+
   type = TREE_TYPE (object);
   bool no_zero_init = true;
   bool zero_padding_bits = false;
@@ -8170,12 +8240,13 @@ cxx_eval_store_expression (const constex
                          index);
              *non_constant_p = true;
            }
-         else if (!is_access_expr
-                  || (TREE_CLOBBER_P (init)
-                      && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END)
-                  || (TREE_CODE (t) == MODIFY_EXPR
-                      && CLASS_TYPE_P (inner)
-                      && !type_has_non_deleted_trivial_default_ctor (inner)))
+         else if ((!is_access_expr
+                   || (TREE_CLOBBER_P (init)
+                       && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END)
+                   || (TREE_CODE (t) == MODIFY_EXPR
+                       && CLASS_TYPE_P (inner)
+                       && !type_has_non_deleted_trivial_default_ctor (inner)))
+                  && !start_lifetime)
            {
              /* Diagnose changing active union member after initialization
                 without a valid member access expression, as described in
@@ -8221,6 +8292,10 @@ cxx_eval_store_expression (const constex
            }
          no_zero_init = true;
        }
+      /* If this is the innermost union and we aren't changing active member
+        of it during start_lifetime, we don't need to change anything.  */
+      else if (code == UNION_TYPE && start_lifetime && seen_union == 1)
+       return void_node;
 
       /* Ending the lifetime of the active union member means the union no
         longer has an active member.  */
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj  2026-05-20 08:43:02.579478077 
+0200
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C     2026-05-20 14:26:01.245053195 
+0200
@@ -652,9 +652,9 @@
 #elif __cpp_expansion_statements != 202506
 #  error "__cpp_expansion_statements != 202506"
 #endif
 
 #ifndef __cpp_trivial_union
 #  error "__cpp_trivial_union"
-#elif __cpp_trivial_union != 202502
-#  error "__cpp_trivial_union != 202502"
+#elif __cpp_trivial_union != 202603
+#  error "__cpp_trivial_union != 202603"
 #endif
--- gcc/testsuite/g++.dg/cpp26/trivial-union3.C.jj      2026-05-20 
14:26:01.245486713 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-05-20 14:26:01.245486713 
+0200
@@ -0,0 +1,28 @@
+// P3074R7 - trivial unions (was std::uninitialized<T>)
+// P3726R2 - Adjustments to Union Lifetime Rules
+// { dg-do compile { target c++26 } }
+
+#include <memory>
+#include <string>
+
+template <typename T, size_t N>
+struct FixedVector {
+  union { T storage[N]; };
+  size_t size = 0;
+
+  constexpr FixedVector () { std::start_lifetime (storage); }
+
+  constexpr ~FixedVector() { std::destroy(storage, storage + size); }
+
+  constexpr void push_back(T const& v) { ::new (storage + size) T(v); ++size; }
+};
+
+constexpr size_t
+silly_test ()
+{
+  FixedVector <std::string, 3> v;
+  v.push_back ("some sufficiently longer string");
+  return v.size;
+}
+
+static_assert (silly_test () == 1);
--- gcc/testsuite/g++.dg/cpp26/trivial-union4.C.jj      2026-05-20 
14:26:01.245562152 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-05-20 14:26:01.245562152 
+0200
@@ -0,0 +1,63 @@
+// P3074R7 - trivial unions (was std::uninitialized<T>)
+// P3726R2 - Adjustments to Union Lifetime Rules
+// { dg-do compile { target c++26 } }
+
+namespace std {
+  template <class T>
+  constexpr void
+  start_lifetime (T &r) noexcept
+  {
+    __builtin_start_lifetime (__builtin_addressof (r));
+  }
+}
+
+struct S { int a; };
+union U { int a; S b[3]; };
+union V { int a; U b[2]; };
+
+consteval
+{
+  S s;
+  s.a = 42;
+  std::start_lifetime (s);
+  if (s.a != 42)
+    throw 1;
+  U u;
+  u.b[0].a = 42;
+  std::start_lifetime (u.b);
+  std::start_lifetime (u.b[0]);
+  std::start_lifetime (u.b[2]);
+  u.a = 42;
+  std::start_lifetime (u.b);
+  std::start_lifetime (u.b[0]);
+  std::start_lifetime (u.b[1]);
+  V v;
+  v.a = 42;
+  std::start_lifetime (v.b);
+  std::start_lifetime (v.b[1]);
+  v.b[1].a = 42;
+  std::start_lifetime (v.b[1].b);
+  std::start_lifetime (v.b[1].b[1]);
+  v.b[1].b[1].a = 43;
+  std::start_lifetime (v.b);
+  std::start_lifetime (v.b[1].b[1]);
+  if (v.b[1].b[1].a != 43)
+    throw 2;
+  const S cs = { 42 };
+  std::start_lifetime (cs);
+  if (cs.a != 42)
+    throw 3;
+  const U cu = { .b = { { 41 }, { 42 }, { 43 } } };
+  std::start_lifetime (cu.b);
+  std::start_lifetime (cu.b[0]);
+  std::start_lifetime (cu.b[2]);
+  if (cu.b[2].a != 43)
+    throw 4;
+  const V cv = { .b = { { .b = { { 44 } } } } };
+  std::start_lifetime (cv.b);
+  std::start_lifetime (cv.b[0]);
+  std::start_lifetime (cv.b[0].b);
+  std::start_lifetime (cv.b[0].b[0]);
+  if (cv.b[0].b[0].a != 44)
+    throw 5;
+}
--- gcc/testsuite/g++.dg/cpp26/trivial-union5.C.jj      2026-05-20 
14:26:01.245655416 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-05-20 14:26:01.245655416 
+0200
@@ -0,0 +1,37 @@
+// P3074R7 - trivial unions (was std::uninitialized<T>)
+// P3726R2 - Adjustments to Union Lifetime Rules
+// { dg-do compile { target c++26 } }
+
+struct S { int a; };
+S a = { 5 };
+struct T { constexpr T () : a (42) {} int a; };
+struct R { int a; constexpr ~R () {} };
+union U { int a; S b[2]; };
+
+consteval { int a = 5; __builtin_start_lifetime (&a); }                // { 
dg-error "'__builtin_start_lifetime' argument type 'int\\\*' is not a pointer 
to aggregate type" }
+consteval { __builtin_start_lifetime (&a); };                  // { dg-error 
"modification of 'a' from outside current evaluation is not a constant 
expression" }
+consteval { __builtin_start_lifetime (); };                    // { dg-error 
"'__builtin_start_lifetime' needs a single argument" }
+consteval { S a = { 6 }; __builtin_start_lifetime (&a, &a); }; // { dg-error 
"'__builtin_start_lifetime' needs a single argument" }
+consteval { __builtin_start_lifetime (42); };                  // { dg-error 
"'__builtin_start_lifetime' argument type 'int' is not pointer type" }
+consteval { T t; __builtin_start_lifetime (&t); }              // { dg-error 
"'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer to aggregate 
type" }
+consteval { R r; __builtin_start_lifetime (&r); }              // { dg-error 
"'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer to 
implicit-lifetime type" }
+consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.a); }        
// { dg-error "'__builtin_start_lifetime' argument type 'const int\\\*' is not 
a pointer to aggregate type" }
+consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.b); }        
// { dg-error "modifying a const object 'u.U::b' is not allowed in a constant 
expression" }
+
+void
+foo ()
+{
+  S s;
+  __builtin_start_lifetime (&s);
+  int a = 5;
+  __builtin_start_lifetime (&a);               // { dg-error 
"'__builtin_start_lifetime' argument type 'int\\\*' is not a pointer to 
aggregate type" }
+  __builtin_start_lifetime (&::a);
+  __builtin_start_lifetime ();                 // { dg-error 
"'__builtin_start_lifetime' needs a single argument" }
+  S b = { 6 };
+  __builtin_start_lifetime (&b, &b);           // { dg-error 
"'__builtin_start_lifetime' needs a single argument" }
+  __builtin_start_lifetime (42);               // { dg-error 
"'__builtin_start_lifetime' argument type 'int' is not pointer type" }
+  T t;
+  __builtin_start_lifetime (&t);               // { dg-error 
"'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer to aggregate 
type" }
+  R r;
+  __builtin_start_lifetime (&r);               // { dg-error 
"'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer to 
implicit-lifetime type" }
+}
--- gcc/testsuite/g++.dg/cpp26/trivial-union6.C.jj      2026-05-20 
14:26:01.245756124 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-05-20 14:26:01.245756124 
+0200
@@ -0,0 +1,26 @@
+// P3074R7 - trivial unions (was std::uninitialized<T>)
+// P3726R2 - Adjustments to Union Lifetime Rules
+// { dg-do compile { target c++20 } }
+
+struct A { int a[2]; };
+union B { int a; int b[2]; };
+union C { int a; int b[2][2]; };
+struct D { B a; };
+struct E { C a; };
+union F { int a; A b; };
+union G { int a; A b[2]; };
+
+constexpr A fA () { A a; a.a[0] = 1; return a; }
+constexpr A a = fA ();         // { dg-error "is not a constant expression" "" 
{ xfail *-*-* } }
+constexpr B fB () { B a; a.b[0] = 1; return a; }
+constexpr B b = fB ();         // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr C fC () { C a; a.b[0][0] = 1; a.b[1][0] = 2; a.b[1][1] = 3; return 
a; }
+constexpr C c = fC ();         // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr D fD () { D a; a.a.b[0] = 1; return a; }
+constexpr D d = fD ();         // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr E fE () { E a; a.a.b[0][0] = 1; a.a.b[1][0] = 2; a.a.b[1][1] = 3; 
return a; }
+constexpr E e = fE ();         // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr F fF () { F a; a.b.a[0] = 1; return a; }
+constexpr F f = fF ();         // { dg-error "is not a constant expression" }
+constexpr G fG () { G a; a.b[0].a[0] = 1; return a; }
+constexpr G g = fG ();         // { dg-error "is not a constant expression" }
--- libstdc++-v3/include/bits/version.def.jj    2026-05-20 08:43:02.584477993 
+0200
+++ libstdc++-v3/include/bits/version.def       2026-05-20 14:26:01.249947842 
+0200
@@ -2410,6 +2410,15 @@ ftms = {
   };
 };
 
+ftms = {
+  name = start_lifetime;
+  values = {
+    v = 202603;
+    cxxmin = 26;
+    extra_cond = "__has_builtin(__builtin_start_lifetime)";
+  };
+};
+
 // Standard test specifications.
 stds[97] = ">= 199711L";
 stds[03] = ">= 199711L";
--- libstdc++-v3/include/bits/version.h.jj      2026-05-20 08:43:02.584477993 
+0200
+++ libstdc++-v3/include/bits/version.h 2026-05-20 14:26:01.250438751 +0200
@@ -2675,4 +2675,14 @@
 #endif /* !defined(__cpp_lib_is_structural) */
 #undef __glibcxx_want_is_structural
 
+#if !defined(__cpp_lib_start_lifetime)
+# if (__cplusplus >  202302L) && (__has_builtin(__builtin_start_lifetime))
+#  define __glibcxx_start_lifetime 202603L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_start_lifetime)
+#   define __cpp_lib_start_lifetime 202603L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_start_lifetime) */
+#undef __glibcxx_want_start_lifetime
+
 #undef __glibcxx_want_all
--- libstdc++-v3/include/bits/stl_construct.h.jj        2026-03-27 
10:17:22.883187971 +0100
+++ libstdc++-v3/include/bits/stl_construct.h   2026-05-20 14:30:52.025619611 
+0200
@@ -404,6 +404,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 #endif // C++23
 
+#if __glibcxx_start_lifetime >= 202603L // C++ >= 26
+  template <typename _Tp>
+    constexpr void
+    start_lifetime(_Tp& __r) noexcept
+    {
+#ifdef __cpp_lib_is_implicit_lifetime
+      static_assert(std::is_implicit_lifetime_v<_Tp>);
+#endif
+#ifdef __cpp_lib_is_aggregate
+      static_assert(std::is_aggregate_v<_Tp>);
+#endif
+      __builtin_start_lifetime(__builtin_addressof(__r));
+    }
+#endif // C++26
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 
--- libstdc++-v3/include/std/memory.jj  2026-05-20 08:43:02.586477959 +0200
+++ libstdc++-v3/include/std/memory     2026-05-20 14:28:05.835373954 +0200
@@ -126,6 +126,7 @@
 #define __glibcxx_want_transparent_operators
 #define __glibcxx_want_smart_ptr_owner_equality
 #define __glibcxx_want_allocate_at_least
+#define __glibcxx_want_start_lifetime
 #include <bits/version.h>
 
 #if __cplusplus >= 201103L && __cplusplus <= 202002L && _GLIBCXX_HOSTED
--- libstdc++-v3/src/c++23/std.cc.in.jj 2026-05-20 08:43:02.591477875 +0200
+++ libstdc++-v3/src/c++23/std.cc.in    2026-05-20 14:26:01.251438735 +0200
@@ -2062,6 +2062,9 @@ export namespace std
 #ifdef __glibcxx_allocate_at_least
   using std::allocation_result;
 #endif
+#if __cpp_lib_start_lifetime
+  using std::start_lifetime;
+#endif
 }
 
 // 20.4 <memory_resource>

        Jakub

Reply via email to