Hi!

Here is an updated version of the P3726R2 paper implementation, originally
posted in May together with the other trivial union paper and one CWG:
https://gcc.gnu.org/pipermail/gcc-patches/2026-May/thread.html#717349
That other paper and CWG has been committed in the meantime, this one
was deferred so that more accurate lifetime tracking is implemented (which
was partially done in the recently committed std::is_within_lifetime patch).

The major change from the earlier patch is that a new CONSTRUCTOR flag
CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P is added, which represents
both the C++20 std::allocator<T>::allocate required behavior as well as
P3726R2 std::start_lifetime, where the CONSTRUCTOR itself is considered
to be within lifetime, but none of its subobjects.
This flag is then tested in reduced_constant_expression_p to determine
which union elemental subobjects are actually inactive and only those are
allowed to be omitted, and in the recently added
__builtin_is_within_lifetime builtin constant evaluation.

So far lightly tested, with:
GXX_TESTSUITE_STDS=98,11,14,17,20,23,26,29 make check-g++ 
RUNTESTFLAGS="dg.exp='within-lifetime* trivial-union* feat-cxx2* 
constexpr-life*'"
ok for trunk if it passes full bootstrap/regtest?

2026-09-04  Jakub Jelinek  <[email protected]>

        P3726R2
        PR c++/125845
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.
        (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P): Define.
        (enum cp_built_in_function): Add CP_BUILT_IN_START_LIFETIME.
        (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 (finish_call_expr): Likewise.
        * constexpr.cc (cxx_eval_is_within_lifetime): For subobjects of
        CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P return boolean_false_node.
        (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 which aren't within lifetime.
        (cxx_eval_store_expression): Add start_lifetime argument.  Don't
        preevaluate init in tha tcase, return void_node early if *valp
        is already non-NULL/void_node and within lifetime.
        (cxx_eval_constant_expression): For std::allocator<T>::allocate
        start lifetime of the created array but not of its subobjects.
gcc/testsuite/
        * g++.dg/cpp26/feat-cxx26.C: Adjust __cpp_trivial_union checking.
        * g++.dg/cpp29/feat-cxx29.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.
        * g++.dg/cpp26/within-lifetime3.C: Remove #if 0 and #endif.
        * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking.
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.

--- a/gcc/c-family/c-cppbuiltin.cc      2026-09-03 09:43:50.763477848 +0200
+++ b/gcc/c-family/c-cppbuiltin.cc      2026-09-04 13:54:14.074058589 +0200
@@ -1124,7 +1124,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 (cxx_dialect > cxx26)
        {
--- a/gcc/cp/cp-tree.h  2026-09-03 09:45:43.310945741 +0200
+++ b/gcc/cp/cp-tree.h  2026-09-04 14:11:50.412816957 +0200
@@ -5098,6 +5098,10 @@ get_vec_init_expr (tree t)
 #define CONSTRUCTOR_BRACES_ELIDED_P(NODE) \
   (CONSTRUCTOR_CHECK (NODE)->base.protected_flag)
 
+/* True if omitted fields are considered to be not within lifetime.  */
+#define CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P(NODE) \
+  (CONSTRUCTOR_CHECK (NODE)->base.deprecated_flag)
+
 /* True if NODE represents a conversion for direct-initialization in a
    template.  Set by perform_implicit_conversion_flags.  */
 #define IMPLICIT_CONV_EXPR_DIRECT_INIT(NODE) \
@@ -7212,6 +7216,7 @@ enum cp_built_in_function {
   CP_BUILT_IN_CURRENT_EXCEPTION,
   CP_BUILT_IN_UNCAUGHT_EXCEPTIONS,
   CP_BUILT_IN_IS_WITHIN_LIFETIME,
+  CP_BUILT_IN_START_LIFETIME,
   CP_BUILT_IN_LAST
 };
 
@@ -9413,7 +9418,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);
--- a/gcc/cp/tree.cc    2026-09-03 09:43:50.789477496 +0200
+++ b/gcc/cp/tree.cc    2026-09-04 13:54:14.075685154 +0200
@@ -579,6 +579,7 @@ builtin_valid_in_constant_expr_p (const_
          case CP_BUILT_IN_CURRENT_EXCEPTION:
          case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS:
          case CP_BUILT_IN_IS_WITHIN_LIFETIME:
+         case CP_BUILT_IN_START_LIFETIME:
            return true;
          default:
            break;
--- a/gcc/cp/decl.cc    2026-09-03 09:45:43.308945768 +0200
+++ b/gcc/cp/decl.cc    2026-09-04 13:54:14.077036599 +0200
@@ -5679,6 +5679,13 @@ cxx_init_decl_processing (void)
   set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
   SET_DECL_IMMEDIATE_FUNCTION_P (decl);
 
+  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.  */
--- a/gcc/cp/cp-gimplify.cc     2026-09-03 09:43:50.774477699 +0200
+++ b/gcc/cp/cp-gimplify.cc     2026-09-04 13:54:14.078081642 +0200
@@ -1036,6 +1036,7 @@ cp_gimplify_expr (tree *expr_p, gimple_s
                                                                    0));
                break;
              case CP_BUILT_IN_CONSTEXPR_DIAG:
+             case CP_BUILT_IN_START_LIFETIME:
                *expr_p = void_node;
                break;
              default:
--- a/gcc/cp/semantics.cc       2026-09-03 09:45:43.312945713 +0200
+++ b/gcc/cp/semantics.cc       2026-09-04 14:10:43.423660866 +0200
@@ -3541,30 +3541,74 @@ finish_call_expr (tree fn, vec<tree, va_
            }
 
          if (TREE_CODE (fn) == FUNCTION_DECL
-             && fndecl_built_in_p (fn, CP_BUILT_IN_IS_WITHIN_LIFETIME,
-                                   BUILT_IN_FRONTEND))
-           {
-             /* Unless users call the builtin directly, the following 2 checks
-                should be ensured from std::is_within_lifetime template.  */
-             if (vec_safe_length (*args) != 1)
-               {
-                 if (complain & tf_error)
-                   error ("%<__builtin_is_within_lifetime%> needs a single "
-                          "argument");
+             && fndecl_built_in_p (fn, BUILT_IN_FRONTEND))
+           switch (DECL_UNCHECKED_FUNCTION_CODE (fn))
+             {
+               tree arg, ptype, type;
+             case CP_BUILT_IN_IS_WITHIN_LIFETIME:
+               /* Unless users call the builtin directly, the following 2 
checks
+                  should be ensured from std::is_within_lifetime template.  */
+               if (vec_safe_length (*args) != 1)
+                 {
+                   if (complain & tf_error)
+                     error ("%qs needs a single argument",
+                            "__builtin_is_within_lifetime");
+                   return error_mark_node;
+                 }
+               arg = (**args)[0];
+               if (error_operand_p (arg))
                  return error_mark_node;
-               }
-             tree arg = (**args)[0];
-             if (error_operand_p (arg))
-               return error_mark_node;
-             tree ptype = TREE_TYPE (arg);
-             if (!POINTER_TYPE_P (ptype))
-               {
-                 if (complain & tf_error)
-                   error ("%<__builtin_is_within_lifetime%> argument type "
-                          "%qT is not pointer type", ptype);
+               ptype = TREE_TYPE (arg);
+               if (!POINTER_TYPE_P (ptype))
+                 {
+                   if (complain & tf_error)
+                     error ("%qs argument type %qT is not pointer type",
+                            "__builtin_is_within_lifetime", ptype);
+                   return error_mark_node;
+                 }
+               break;
+             case CP_BUILT_IN_START_LIFETIME:
+               /* Unless users call the builtin directly, the following 2 
checks
+                  should be ensured from std::start_lifetime template.  */
+               if (vec_safe_length (*args) != 1)
+                 {
+                   if (complain & tf_error)
+                     error ("%qs needs a single argument",
+                            "__builtin_start_lifetime");
+                   return error_mark_node;
+                 }
+               arg = (**args)[0];
+               if (error_operand_p (arg))
                  return error_mark_node;
-               }
-           }
+               ptype = TREE_TYPE (arg);
+               if (!POINTER_TYPE_P (ptype))
+                 {
+                   if (complain & tf_error)
+                     error ("%qs argument type %qT is not pointer type",
+                            "__builtin_start_lifetime", ptype);
+                   return error_mark_node;
+                 }
+               type = TREE_TYPE (ptype);
+               if (!complete_type_or_else (type, NULL_TREE))
+                 return error_mark_node;
+               if (!CP_AGGREGATE_TYPE_P (type))
+                 {
+                   if (complain & tf_error)
+                     error ("%qs argument type %qT is not a pointer to "
+                            "aggregate type", "__builtin_start_lifetime",
+                            ptype);
+                   return error_mark_node;
+                 }
+               if (!implicit_lifetime_type_p (type))
+                 {
+                   if (complain & tf_error)
+                     error ("%qs argument type %qT is not a pointer to "
+                            "implicit-lifetime type",
+                            "__builtin_start_lifetime", ptype);
+                   return error_mark_node;
+                 }
+               break;
+             }
 
          /* A call to a namespace-scope function.  */
          result = build_new_function_call (fn, args, orig_complain);
--- a/gcc/cp/constexpr.cc       2026-09-03 09:43:50.766477808 +0200
+++ b/gcc/cp/constexpr.cc       2026-09-04 14:32:59.700779280 +0200
@@ -2748,7 +2748,11 @@ cxx_eval_is_within_lifetime (const const
        if (ref == NULL_TREE)
          continue;
        if (CONSTRUCTOR_NO_CLEARING (val))
-         val = val_uninit;
+         {
+           if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val))
+             return boolean_false_node;
+           val = val_uninit;
+         }
        else
          val = val_zero_init;
        continue;
@@ -2775,7 +2779,11 @@ cxx_eval_is_within_lifetime (const const
            continue;
          }
        if (CONSTRUCTOR_NO_CLEARING (val))
-         val = val_uninit;
+         {
+           if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val))
+             return boolean_false_node;
+           val = val_uninit;
+         }
        else
          val = val_zero_init;
        continue;
@@ -2785,6 +2793,41 @@ cxx_eval_is_within_lifetime (const const
   return boolean_true_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);
+  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;
+  CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1;
+  tree modexpr = build2_loc (loc, 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.  */
@@ -2862,6 +2905,10 @@ cxx_eval_builtin_function_call (const co
        return cxx_eval_is_within_lifetime (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;
       }
@@ -5107,10 +5154,13 @@ cxx_eval_call_expression (const constexp
    initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will be
    cleared.  If called recursively on a FIELD_DECL's CONSTRUCTOR, SZ
    is DECL_SIZE of the FIELD_DECL, otherwise NULL.
+   UNION_ELEMENTAL_SUBOBJECT is true when recursing on C++ >= 26 union
+   elemental subobjects.
    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;
@@ -5134,7 +5184,11 @@ 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
+             && CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (t))
+           field = NULL_TREE;
+         else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE)
            {
              /* There must be a valid constant initializer at every array
                 index.  */
@@ -5168,12 +5222,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
@@ -5183,7 +5250,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.  */
@@ -8213,13 +8281,14 @@ modifying_const_object_p (tree_code code
   return false;
 }
 
-/* Evaluate an INIT_EXPR or MODIFY_EXPR.  */
+/* Evaluate an INIT_EXPR or MODIFY_EXPR.  START_LIFETIME is true
+   when evaluating __builtin_start_lifetime.  */
 
 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;
 
@@ -8232,7 +8301,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
@@ -8573,12 +8642,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
@@ -8675,6 +8745,11 @@ cxx_eval_store_expression (const constex
       empty_base = true;
     }
 
+  /* For __builtin_start_lifetime, if it is already within lifetime,
+     don't do anything.  */
+  if (start_lifetime && *valp != NULL_TREE && *valp != void_node)
+    return void_node;
+
   /* Detect modifying a constant object in constexpr evaluation.
      We have found a const object that is being modified.  Figure out
      if we need to issue an error.  Consider
@@ -8780,7 +8855,7 @@ cxx_eval_store_expression (const constex
                       || *valp == void_node
                       || (same_type_ignoring_top_level_qualifiers_p
                           (TREE_TYPE (*valp), type)));
-  if (empty_base)
+  if (empty_base && !start_lifetime)
     {
       /* Just evaluate the initializer and return, since there's no actual data
         to store, and we didn't build a CONSTRUCTOR.  */
@@ -10730,6 +10805,17 @@ cxx_eval_constant_expression (const cons
            TREE_TYPE (var) = new_type;
            TREE_TYPE (TREE_OPERAND (op, 0))
              = build_pointer_type (TREE_TYPE (var));
+           if (is_std_allocator_allocate (ctx->call)
+               && id_equal (DECL_NAME (ctx->call->fundef->decl), "allocate"))
+             {
+               /* [allocator.members]/5: This function starts the lifetime
+                  of the array object, but not that of any of the array
+                  elements.  */
+               tree ctor = build_constructor (new_type, NULL);
+               CONSTRUCTOR_NO_CLEARING (ctor) = 1;
+               CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1;
+               ctx->global->put_value (var, ctor);
+             }
          }
 
        /* This can happen for std::meta::info(^^int) where the cast has no
--- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C   2026-09-03 09:43:50.797477387 
+0200
+++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C   2026-09-04 13:54:14.080797712 
+0200
@@ -655,8 +655,8 @@
 
 #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
 
 // C++26 attributes:
--- a/gcc/testsuite/g++.dg/cpp26/trivial-union3.C       2026-09-04 
13:54:14.080919178 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union3.C       2026-09-04 
13:54:14.080919178 +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);
--- a/gcc/testsuite/g++.dg/cpp26/trivial-union4.C       2026-09-04 
13:54:14.081027425 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union4.C       2026-09-04 
13:54:14.081027425 +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;
+}
--- a/gcc/testsuite/g++.dg/cpp26/trivial-union5.C       2026-09-04 
13:54:14.081120178 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union5.C       2026-09-04 
13:54:14.081120178 +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" }
+}
--- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-04 
13:54:14.081215755 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-04 
13:54:14.081215755 +0200
@@ -0,0 +1,70 @@
+// 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" }
+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" }
+constexpr D fD () { D a; a.a.b[0] = 1; return a; }
+constexpr D d = fD ();         // { dg-error "is not a constant expression" }
+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" }
+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" }
+constexpr B
+fB2 ()
+{
+  B a;
+  __builtin_start_lifetime (&a.b);
+  a.b[0] = 1;
+  return a;
+}
+constexpr B b2 = fB2 ();       // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr C
+fC2 ()
+{
+  C a;
+  __builtin_start_lifetime (&a.b);
+  __builtin_start_lifetime (&a.b[0]);
+  __builtin_start_lifetime (&a.b[1]);
+  a.b[0][0] = 1;
+  a.b[1][0] = 2;
+  a.b[1][1] = 3;
+  return a;
+}
+constexpr C c2 = fC2 ();       // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr D
+fD2 ()
+{
+  D a;
+  __builtin_start_lifetime (&a.a.b);
+  a.a.b[0] = 1;
+  return a;
+}
+constexpr D d2 = fD2 ();       // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr E
+fE2 ()
+{
+  E a;
+  __builtin_start_lifetime (&a.a.b);
+  __builtin_start_lifetime (&a.a.b[0]);
+  __builtin_start_lifetime (&a.a.b[1]);
+  a.a.b[0][0] = 1;
+  a.a.b[1][0] = 2;
+  a.a.b[1][1] = 3;
+  return a;
+}
+constexpr E e2 = fE2 ();       // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C     2026-09-03 
09:43:50.797477387 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C     2026-09-04 
13:54:14.081341081 +0200
@@ -17,12 +17,10 @@ foo (bool x)
 {
   std::allocator <int> a;
   auto b = a.allocate (1);
-#if 0
   // [allocator.members]/5 says it should start lifetime of the array
   // but not its elements, and b points to the first element.
   if (std::is_within_lifetime (b))
     return false;
-#endif
   std::construct_at (b);
   if (!std::is_within_lifetime (b))
     return false;
--- a/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C   2026-09-03 09:43:50.797477387 
+0200
+++ b/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C   2026-09-04 13:54:14.081504434 
+0200
@@ -655,8 +655,8 @@
 
 #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
 
 // C++26 attributes:
--- a/libstdc++-v3/include/bits/version.def     2026-09-03 09:43:50.805477279 
+0200
+++ b/libstdc++-v3/include/bits/version.def     2026-09-04 13:54:14.114036138 
+0200
@@ -2661,6 +2661,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";
--- a/libstdc++-v3/include/bits/version.h       2026-09-03 09:43:50.805477279 
+0200
+++ b/libstdc++-v3/include/bits/version.h       2026-09-04 13:54:14.115036126 
+0200
@@ -2949,4 +2949,14 @@
 #endif /* !defined(__cpp_lib_hardened_view_interface) */
 #undef __glibcxx_want_hardened_view_interface
 
+#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
--- a/libstdc++-v3/include/bits/stl_construct.h 2026-09-03 09:43:50.803477306 
+0200
+++ b/libstdc++-v3/include/bits/stl_construct.h 2026-09-04 13:54:14.116046285 
+0200
@@ -412,6 +412,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
 
--- a/libstdc++-v3/include/std/memory   2026-09-03 09:43:50.811477198 +0200
+++ b/libstdc++-v3/include/std/memory   2026-09-04 13:54:14.116326618 +0200
@@ -127,6 +127,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
--- a/libstdc++-v3/src/c++23/std.cc.in  2026-09-03 09:43:50.811477198 +0200
+++ b/libstdc++-v3/src/c++23/std.cc.in  2026-09-04 13:54:14.116872978 +0200
@@ -2074,6 +2074,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