Hi!

Thanks for all the review comments.

On Wed, Sep 09, 2026 at 03:15:54PM -0400, Jason Merrill wrote:
> > > +       case CP_BUILT_IN_START_LIFETIME:
> > >                   *expr_p = void_node;
> > 
> > Is it safe to drop this when the argument has side-effects?

Changed that to fold_convert to void_type_node.  For uses in
std::start_lifetime it shouldn't make a difference, but when people
use the builtin directly it matters.

> > seems to duplicate the previous case almost verbatim.
> 
> Indeed, and perhaps we could use DECL_NAME (fn) instead of extra string
> literals in the diagnostics?

Shared what could be shared and used DECL_NAME (fn), both here and
in constexpr.cc too.

> > > +      type = TREE_TYPE (ptype);
> > > +      if (!complete_type_or_else (type, NULL_TREE))
> > 
> > This emits a diagnostic unconditionally, should it be
> > complete_type_or_maybe_complain?  Elsewhere this function
> > checks complain first.
> Good point.

Done too.

2026-09-09  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.  Document some generic flags used in C++ specific way on some
        trees in the toplevel comment.
        (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 (check_frontend_builtin): New function.
        (finish_call_expr): Use it.
        * constexpr.cc (is_within_lifetime): New function.
        (cxx_eval_is_within_lifetime): Use it.  Add fun argument and pass it
        to that function.
        (cxx_eval_start_lifetime): New function.
        (cxx_eval_builtin_function_call): Handle CP_BUILT_IN_START_LIFETIME.
        Pass fun to cxx_eval_is_within_lifetime.
        (is_std_allocator_allocate): Only return true for
        std::allocator<T>::allocate and not for std::allocator<T>::deallocate.
        (is_std_allocator_allocate_deallocate): New functions.
        (cxx_eval_call_expression): Use is_std_allocator_allocate_deallocate
        rather than is_std_allocator_allocate.
        (reduced_constant_expression_p): Add union_elemental_subobject
        argument and propagate it to recursive calls.  If set, ignore for
        C++26 holes in array initializers which aren't within lifetime.
        (modifying_const_object_p): Change != MODIFY_EXPR test to == INIT_EXPR.
        (cxx_eval_store_expression): Handle MODOP_EXPR next to INIT_EXPR and
        MODIFY_EXPR, meaning start_lifetime.  Don't preevaluate init in that
        case, 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.
        Use is_std_allocator_allocate_deallocate rather than
        is_std_allocator_allocate where appropriate.
        (potential_constant_expression_1): Likewise.
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.
        * g++.dg/cpp26/within-lifetime3.C: Remove #if 0 and #endif.
        * g++.dg/cpp26/within-lifetime8.C: New test.
        * g++.dg/cpp26/within-lifetime9.C: New test.
        * 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-09 17:03:29.365511237 +0200
+++ b/gcc/c-family/c-cppbuiltin.cc      2026-09-09 17:08:09.016033509 +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-09 17:03:29.376511095 +0200
+++ b/gcc/cp/cp-tree.h  2026-09-09 17:08:09.016805380 +0200
@@ -644,7 +644,22 @@ extern GTY(()) tree cp_global_trees[CPTI
 
      Temporarily, it may be set to a TREE_LIST whose TREE_VALUE is
      the virtual function this one overrides, and whose TREE_CHAIN is
-     the old DECL_VINDEX.  */
+     the old DECL_VINDEX.
+
+   TREE_PRIVATE
+     For CONSTRUCTOR, this is CONSTRUCTOR_IS_PAREN_INIT.
+
+   TREE_PROTECTED
+     For CONSTRUCTOR, this is CONSTRUCTOR_BRACES_ELIDED_P.
+     For IDENTIFIER, this is IDENTIFIER_LAMBDA_P.
+     For AGGR_INIT_EXPR, this is AGGR_INIT_FROM_THUNK_P.
+     For ADDR_EXPR, this is ADDR_EXPR_DENOTES_CALL_P.
+
+   TREE_STATIC
+     For AGGR_INIT_EXPR, this is AGGR_INIT_EXPR_MUST_TAIL.
+
+   TREE_DEPRECATED
+     For CONSTRUCTOR, this is CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P.  */
 
 /* Language-specific tree checkers.  */
 
@@ -5099,6 +5114,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) \
@@ -7231,6 +7250,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
 };
 
@@ -9432,7 +9452,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-09 17:03:29.394510863 +0200
+++ b/gcc/cp/tree.cc    2026-09-09 17:08:09.017716173 +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-09 17:03:29.377511082 +0200
+++ b/gcc/cp/decl.cc    2026-09-09 17:08:09.018991479 +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 (void_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 (integer_type_node, 2);
 
   /* Guess at the initial static decls size.  */
--- a/gcc/cp/cp-gimplify.cc     2026-09-09 17:03:29.375511108 +0200
+++ b/gcc/cp/cp-gimplify.cc     2026-09-09 22:31:38.233689704 +0200
@@ -1042,6 +1042,10 @@ 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:
+               *expr_p = fold_convert (void_type_node,
+                                       CALL_EXPR_ARG (*expr_p, 0));
+               break;
              default:
                break;
              }
--- a/gcc/cp/semantics.cc       2026-09-09 17:03:29.386510966 +0200
+++ b/gcc/cp/semantics.cc       2026-09-09 22:41:50.076627312 +0200
@@ -3300,6 +3300,63 @@ perform_koenig_lookup (cp_expr fn_expr,
   return cp_expr (fn, loc);
 }
 
+/* Analyze call to a front-end builtin FN with arguments *ARGS.  Return true
+   if the call is correct, false if erroneous.  */
+
+static bool
+check_frontend_builtin (tree fn, vec<tree, va_gc> **args,
+                       tsubst_flags_t complain)
+{
+  tree arg, ptype, type;
+  switch (DECL_UNCHECKED_FUNCTION_CODE (fn))
+    {
+    case CP_BUILT_IN_IS_WITHIN_LIFETIME:
+    case CP_BUILT_IN_START_LIFETIME:
+      /* Unless users call the builtin directly, the following 2 checks
+        should be ensured from std::is_within_lifetime or
+        std::start_lifetime template.  */
+      if (vec_safe_length (*args) != 1)
+       {
+         if (complain & tf_error)
+           error ("%qE needs a single argument", DECL_NAME (fn));
+         return false;
+       }
+      arg = (**args)[0];
+      if (error_operand_p (arg))
+       return false;
+      ptype = TREE_TYPE (arg);
+      if (!POINTER_TYPE_P (ptype))
+       {
+         if (complain & tf_error)
+           error ("%qE argument type %qT is not pointer type",
+                  DECL_NAME (fn), ptype);
+         return false;
+       }
+      if (DECL_UNCHECKED_FUNCTION_CODE (fn) == CP_BUILT_IN_IS_WITHIN_LIFETIME)
+       return true;
+      type = TREE_TYPE (ptype);
+      if (!complete_type_or_maybe_complain (type, NULL_TREE, complain))
+       return false;
+      if (!CP_AGGREGATE_TYPE_P (type))
+       {
+         if (complain & tf_error)
+           error ("%qE argument type %qT is not a pointer to aggregate type",
+                  DECL_NAME (fn), ptype);
+         return false;
+       }
+      if (!implicit_lifetime_type_p (type))
+       {
+         if (complain & tf_error)
+           error ("%qE argument type %qT is not a pointer to "
+                  "implicit-lifetime type", DECL_NAME (fn), ptype);
+         return false;
+       }
+      return true;
+    default:
+      return true;
+    }
+}
+
 /* Generate an expression for `FN (ARGS)'.  This may change the
    contents of ARGS.
 
@@ -3541,30 +3598,9 @@ 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");
-                 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);
-                 return error_mark_node;
-               }
-           }
+             && fndecl_built_in_p (fn, BUILT_IN_FRONTEND)
+             && !check_frontend_builtin (fn, args, complain))
+           return error_mark_node;
 
          /* A call to a namespace-scope function.  */
          result = build_new_function_call (fn, args, orig_complain);
--- a/gcc/cp/constexpr.cc       2026-09-09 17:03:29.374511120 +0200
+++ b/gcc/cp/constexpr.cc       2026-09-09 22:41:08.379178946 +0200
@@ -2541,62 +2541,14 @@ 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 fun,
+                   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))
@@ -2635,22 +2587,19 @@ cxx_eval_is_within_lifetime (const const
          auto_diagnostic_group d;
          if (DECL_P (obj) && DECL_NAME (obj) == heap_deleted_identifier)
            {
-             error_at (loc, "%qs on allocated storage after deallocation "
-                       "is not a constant expression",
-                       "__builtin_is_within_lifetime");
+             error_at (loc, "%qE on allocated storage after deallocation "
+                       "is not a constant expression", DECL_NAME (fun));
              inform (DECL_SOURCE_LOCATION (obj), "allocated here");
            }
          else if (DECL_P (obj) && ctx->global->is_outside_lifetime (obj))
            {
-             error_at (loc, "%qs on %qE after its storage has been released "
-                       "is not a constant expression",
-                       "__builtin_is_within_lifetime", obj);
+             error_at (loc, "%qE on %qE after its storage has been released "
+                       "is not a constant expression", DECL_NAME (fun), obj);
              inform (DECL_SOURCE_LOCATION (obj), "declared here");
            }
          else
-           error_at (loc, "%qs on %qE from outside current evaluation "
-                     "is not a constant expression",
-                     "__builtin_is_within_lifetime", obj);
+           error_at (loc, "%qE on %qE from outside current evaluation "
+                     "is not a constant expression", DECL_NAME (fun), obj);
        }
       *non_constant_p = true;
       return t;
@@ -2687,9 +2636,8 @@ cxx_eval_is_within_lifetime (const const
        if (check_mutable && DECL_MUTABLE_P (TREE_OPERAND (ref, 1)))
          {
            if (!ctx->quiet)
-             error_at (loc, "%qs on %<mutable%> sub-object %qD",
-                       "__builtin_is_within_lifetime",
-                       TREE_OPERAND (ref, 1));
+             error_at (loc, "%qE on %<mutable%> sub-object %qD",
+                       DECL_NAME (fun), TREE_OPERAND (ref, 1));
            *non_constant_p = true;
            return t;
          }
@@ -2748,7 +2696,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 +2727,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 +2741,135 @@ 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, tree fun,
+                            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, "%qE called with a null pointer",
+                 DECL_NAME (fun));
+      *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, "%qE called with pointer to function",
+                 DECL_NAME (fun));
+      *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, fun, arg,
+                            non_constant_p, overflow_p, jump_target);
+}
+
+static tree cxx_eval_store_expression (const constexpr_ctx *, tree,
+                                      value_cat, bool *, bool *,
+                                      tree *);
+
+/* Attempt to evaluate T which represents a call to
+   __builtin_start_lifetime.  */
+
+static tree
+cxx_eval_start_lifetime (const constexpr_ctx *ctx, tree t, tree fun,
+                        bool *non_constant_p, bool *overflow_p,
+                        tree *jump_target)
+{
+  location_t loc = EXPR_LOCATION (t);
+  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,
+                                     jump_target);
+  if (*jump_target)
+    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, fun, 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, "%qE with containing object not within lifetime",
+                     DECL_NAME (fun));
+         *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;
+  tree modopexpr
+    = build3_loc (loc, MODOP_EXPR, TREE_TYPE (arg), arg, ctor, NULL_TREE);
+  arg = cxx_eval_store_expression (ctx, modopexpr, vc_discard, non_constant_p,
+                                  overflow_p, jump_target);
+  ggc_free (modopexpr);
+  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.  */
@@ -2859,8 +2944,12 @@ cxx_eval_builtin_function_call (const co
                                        jump_target);
 
       case CP_BUILT_IN_IS_WITHIN_LIFETIME:
-       return cxx_eval_is_within_lifetime (ctx, t, non_constant_p, overflow_p,
-                                           jump_target);
+       return cxx_eval_is_within_lifetime (ctx, t, fun, non_constant_p,
+                                           overflow_p, jump_target);
+
+      case CP_BUILT_IN_START_LIFETIME:
+       return cxx_eval_start_lifetime (ctx, t, fun, non_constant_p,
+                                       overflow_p, jump_target);
 
       default:
        break;
@@ -3780,14 +3869,13 @@ is_std_allocator (tree ctx)
   return is_std_class (ctx, "allocator");
 }
 
-/* Return true if FNDECL is std::allocator<T>::{,de}allocate.  */
+/* Return true if FNDECL is std::allocator<T>::allocate.  */
 
 static bool
 is_std_allocator_allocate (tree fndecl)
 {
   tree name = DECL_NAME (fndecl);
-  if (name == NULL_TREE
-      || !(id_equal (name, "allocate") || id_equal (name, "deallocate")))
+  if (name == NULL_TREE || !id_equal (name, "allocate"))
     return false;
 
   return is_std_allocator (DECL_CONTEXT (fndecl));
@@ -3803,6 +3891,29 @@ is_std_allocator_allocate (const constex
          && is_std_allocator_allocate (call->fundef->decl));
 }
 
+/* Return true if FNDECL is std::allocator<T>::{,de}allocate.  */
+
+static bool
+is_std_allocator_allocate_deallocate (tree fndecl)
+{
+  tree name = DECL_NAME (fndecl);
+  if (name == NULL_TREE
+      || !(id_equal (name, "allocate") || id_equal (name, "deallocate")))
+    return false;
+
+  return is_std_allocator (DECL_CONTEXT (fndecl));
+}
+
+/* Overload for the above taking constexpr_call*.  */
+
+static inline bool
+is_std_allocator_allocate_deallocate (const constexpr_call *call)
+{
+  return (call
+         && call->fundef
+         && is_std_allocator_allocate_deallocate (call->fundef->decl));
+}
+
 /* Return true if FNDECL is std::source_location::current.  */
 
 static inline bool
@@ -4433,7 +4544,7 @@ cxx_eval_call_expression (const constexp
       if (TREE_CODE (t) == CALL_EXPR
          && cxx_replaceable_global_alloc_fn (fun)
          && (CALL_FROM_NEW_OR_DELETE_P (t)
-             || is_std_allocator_allocate (ctx->call)))
+             || is_std_allocator_allocate_deallocate (ctx->call)))
        {
          const bool new_op_p = IDENTIFIER_NEW_OP_P (DECL_NAME (fun));
          const int nargs = call_expr_nargs (t);
@@ -5107,10 +5218,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 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 +5248,12 @@ 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
+             && cxx_dialect >= cxx26
+             && 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.  */
@@ -5143,7 +5262,8 @@ reduced_constant_expression_p (tree t, t
              tree cursor = min;
              for (auto &e: CONSTRUCTOR_ELTS (t))
                {
-                 if (!reduced_constant_expression_p (e.value))
+                 if (!reduced_constant_expression_p
+                       (e.value, NULL_TREE, union_elemental_subobject))
                    return false;
                  if (array_index_cmp (cursor, e.index) != 0)
                    return false;
@@ -5168,12 +5288,22 @@ 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 = true;
            }
          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 (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 +5313,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.  */
@@ -8186,7 +8317,7 @@ static bool
 modifying_const_object_p (tree_code code, tree obj, bool mutable_p)
 {
   /* If this is initialization, there's no problem.  */
-  if (code != MODIFY_EXPR)
+  if (code == INIT_EXPR)
     return false;
 
   /* [basic.type.qualifier] "A const object is an object of type
@@ -8213,7 +8344,9 @@ modifying_const_object_p (tree_code code
   return false;
 }
 
-/* Evaluate an INIT_EXPR or MODIFY_EXPR.  */
+/* Evaluate an INIT_EXPR or MODIFY_EXPR or MODOP_EXPR.  MODOP_EXPR means
+   we are evaluating __builtin_start_lifetime (init is in the second
+   operand rather than third, like for INIT_EXPR and MODIFY_EXPR).  */
 
 static tree
 cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
@@ -8231,8 +8364,8 @@ cxx_eval_store_expression (const constex
   maybe_simplify_trivial_copy (target, init);
 
   tree type = TREE_TYPE (target);
-  bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR;
-  if (preeval && !TREE_CLOBBER_P (init))
+  bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) != INIT_EXPR;
+  if (preeval && !TREE_CLOBBER_P (init) && TREE_CODE (t) != MODOP_EXPR)
     {
       /* Ignore var = .DEFERRED_INIT (); for now, until PR121965 is fixed.  */
       if (flag_auto_var_init > AUTO_INIT_UNINITIALIZED
@@ -8573,12 +8706,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) != INIT_EXPR
+                       && CLASS_TYPE_P (inner)
+                       && !type_has_non_deleted_trivial_default_ctor (inner)))
+                  && TREE_CODE (t) != MODOP_EXPR)
            {
              /* Diagnose changing active union member after initialization
                 without a valid member access expression, as described in
@@ -8675,6 +8809,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 (TREE_CODE (t) == MODOP_EXPR && *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
@@ -10565,7 +10704,7 @@ cxx_eval_constant_expression (const cons
               std::source_location::current, we permit casting from void*
               because that is compiler-generated code.  */
            && !is_std_construct_at (ctx->call)
-           && !is_std_allocator_allocate (ctx->call)
+           && !is_std_allocator_allocate_deallocate (ctx->call)
            && !is_std_source_location_current (ctx->call))
          {
            /* Likewise, don't error when casting from void* when OP is
@@ -10730,6 +10869,16 @@ 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))
+             {
+               /* [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
@@ -12475,7 +12624,7 @@ potential_constant_expression_1 (tree t,
                        || TREE_CODE (t) != CALL_EXPR
                        || (!CALL_FROM_NEW_OR_DELETE_P (t)
                            && (current_function_decl == NULL_TREE
-                               || !is_std_allocator_allocate
+                               || !is_std_allocator_allocate_deallocate
                                                (current_function_decl))))
                    /* Allow placement new in std::construct_at.  */
                    && (!cxx_placement_new_fn (fun)
--- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C   2026-09-09 17:03:29.405510721 
+0200
+++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C   2026-09-09 17:08:09.023187822 
+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-09 
17:08:09.023313841 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union3.C       2026-09-09 
17:08:09.023313841 +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-09 
17:08:09.023439796 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union4.C       2026-09-09 
22:45:12.019955720 +0200
@@ -0,0 +1,74 @@
+// P3074R7 - trivial unions (was std::uninitialized<T>)
+// P3726R2 - Adjustments to Union Lifetime Rules
+// { dg-do run { 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]; };
+struct W { S a[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;
+}
+
+int
+main ()
+{
+  W w;
+  int i = 0;
+  __builtin_start_lifetime (&w.a[++i]);
+  if (i != 1)
+    __builtin_abort ();
+}
--- a/gcc/testsuite/g++.dg/cpp26/trivial-union5.C       2026-09-09 
17:08:09.023540149 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union5.C       2026-09-09 
17:08:09.023540149 +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-09 
17:08:09.023638485 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-09 
17:10:56.651679279 +0200
@@ -0,0 +1,167 @@
+// 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 (bool x)
+{
+  C a;
+  __builtin_start_lifetime (&a.b);
+  __builtin_start_lifetime (&a.b[0]);
+  if (x)
+    __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 (false);  // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr C c3 = fC2 (true);   // { 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 (bool x)
+{
+  E a;
+  __builtin_start_lifetime (&a.a.b);
+  __builtin_start_lifetime (&a.a.b[0]);
+  if (x)
+    __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 (false);  // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr E e3 = fE2 (true);   // { dg-error "is not a constant expression" "" 
{ target c++23_down } }
+constexpr E
+fE3 (bool x)
+{
+  E a;
+  __builtin_start_lifetime (&a.a.b[0]);        // { dg-error 
"'__builtin_start_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);
+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_start_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_start_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_start_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_start_lifetime' with containing object not within lifetime" }
+      break;
+    case 8:
+      __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error 
"'__builtin_start_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_start_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_start_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_start_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-lifetime3.C     2026-09-09 
17:03:29.407510696 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C     2026-09-09 
17:08:09.023764764 +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/cpp26/within-lifetime8.C     2026-09-09 
17:08:09.023919133 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C     2026-09-09 
17:08:23.859228524 +0200
@@ -0,0 +1,168 @@
+// P3450R1 - Extend std::is_within_lifetime
+// { dg-do compile { target c++20 } }
+
+namespace std {
+  template <class T>
+  consteval bool
+  is_within_lifetime (const T *p) noexcept
+  {
+    return __builtin_is_within_lifetime (p);
+  }
+
+  template <class T>
+  constexpr void
+  start_lifetime (T &r) noexcept
+  {
+    __builtin_start_lifetime (__builtin_addressof (r));
+  }
+}
+
+struct A { int a, b; };
+struct B { A c, d[2]; };
+union C { B e; int f; };
+struct D { C g; B h; };
+struct E { D i[3]; };
+union F { E j; int k; };
+
+consteval int
+foo ()
+{
+  F l;
+  l.j.i[0].h.c.a = 1;
+  if (!std::is_within_lifetime (&l.j.i[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  l.k = 1;
+  if (std::is_within_lifetime (&l.j.i[1]))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  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__;
+  if (std::is_within_lifetime (&l.j.i[1].g))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  std::start_lifetime (l.j.i[1].g);
+  if (!std::is_within_lifetime (&l.j.i[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  std::start_lifetime (l.j.i[1].h);
+  if (!std::is_within_lifetime (&l.j.i[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  std::start_lifetime (l.j.i[1].h.d);
+  std::start_lifetime (l.j.i[1].h.d[0]);
+  if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  l.j.i[1].h.d[0].a = 42;
+  if (!std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  std::start_lifetime (l.j.i[1].g.e);
+  std::start_lifetime (l.j.i[1].g.e.d);
+  std::start_lifetime (l.j.i[1].g.e.d[1]);
+  if (!std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1]))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  l.j.i[1].g.e.d[1].b = 42;
+  if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  std::start_lifetime (l.j.i[1].g);
+  if (!std::is_within_lifetime (&l.j.i[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  std::start_lifetime (l.j.i[1].g);
+  l.j.i[1].g.f = 42;
+  if (!std::is_within_lifetime (&l.j.i[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h))
+    return __LINE__;
+  if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+    return __LINE__;
+  l.k = 1;
+  return 0;
+}
+
+static_assert (foo () == 0);
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime9.C     2026-09-09 
17:08:09.024047950 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime9.C     2026-09-09 
17:08:09.024047950 +0200
@@ -0,0 +1,104 @@
+// P3450R1 - Extend std::is_within_lifetime
+// { dg-do compile { target c++20 } }
+
+namespace std {
+  template <class T>
+  consteval bool
+  is_within_lifetime (const T *p) noexcept
+  {
+    return __builtin_is_within_lifetime (p);
+  }
+
+  template <class T>
+  constexpr void
+  start_lifetime (T &r) noexcept
+  {
+    __builtin_start_lifetime (__builtin_addressof (r));
+  }
+}
+
+struct A {};
+struct B : A {};
+struct C { B a; };
+union D { A a[3]; B b[3]; C c[3]; };
+
+consteval int
+foo ()
+{
+  union D d;
+  if (std::is_within_lifetime (&d.a[0]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.b[1]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[2]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[2].a))
+    return __LINE__;
+  std::start_lifetime (d.a);
+  std::start_lifetime (d.a[0]);
+  if (!std::is_within_lifetime (&d.a[0]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.b[1]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[2]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[2].a))
+    return __LINE__;
+  std::start_lifetime (d.b);
+  std::start_lifetime (d.b[1]);
+  if (std::is_within_lifetime (&d.a[0]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&d.b[1]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[2]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[2].a))
+    return __LINE__;
+  std::start_lifetime (d.c);
+  std::start_lifetime (d.c[2]);
+  if (std::is_within_lifetime (&d.a[0]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.b[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&d.c[2]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[2].a))
+    return __LINE__;
+  std::start_lifetime (d.c[2].a);
+  if (std::is_within_lifetime (&d.a[0]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.b[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&d.c[2]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&d.c[2].a))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[1]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[1].a))
+    return __LINE__;
+  std::start_lifetime (d.c[1]);
+  if (!std::is_within_lifetime (&d.c[1]))
+    return __LINE__;
+  if (std::is_within_lifetime (&d.c[1].a))
+    return __LINE__;
+  return 0;
+}
+
+static_assert (foo () == 0);
+
+consteval int
+bar ()
+{
+  union D d;
+  std::start_lifetime (d.c);
+  std::start_lifetime (d.c[1]);
+  d.c[1].a = {};
+  if (!std::is_within_lifetime (&d.c[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&d.c[1].a))
+    return __LINE__;
+  return 0;
+}
+
+static_assert (bar () == 0);           // { dg-bogus "static assertion failed" 
"" { xfail *-*-* } }
--- a/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C   2026-09-09 17:03:29.407510696 
+0200
+++ b/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C   2026-09-09 17:08:09.024204742 
+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-09 17:03:29.420510528 
+0200
+++ b/libstdc++-v3/include/bits/version.def     2026-09-09 17:08:09.024435693 
+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-09 17:03:29.420510528 
+0200
+++ b/libstdc++-v3/include/bits/version.h       2026-09-09 17:08:09.024715555 
+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-09 17:03:29.416510580 
+0200
+++ b/libstdc++-v3/include/bits/stl_construct.h 2026-09-09 17:08:09.024980314 
+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-09 17:03:29.420510528 +0200
+++ b/libstdc++-v3/include/std/memory   2026-09-09 17:08:09.025156732 +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-09 17:03:29.420510528 +0200
+++ b/libstdc++-v3/src/c++23/std.cc.in  2026-09-09 17:08:09.025416416 +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