On Mon, Sep 7, 2026 at 1:50 PM Jakub Jelinek <[email protected]> wrote:

> On Fri, Sep 04, 2026 at 04:44:22PM -0400, Jason Merrill wrote:
> > Let's move all the builtin handling out to another function at this
> point.
>
> Done.
>
> > > +/* 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)
> >
> > Let's document the use of these bitfields in the "Usage of
> > language-independent fields in a language-dependent manner" section at
> the
> > top of the file.
>
> Done.
>
> > > --- 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,
> >
> > "void_vaftype" with bool return type seems contradictory.
>
> Changed to void_type_node.
>
> > > @@ -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;
> >
> > I'd prefer to compute union_elemental_subobject regardless of
> cxx_dialect,
> > and only check cxx_dialect when applying it above.
>
> Done.
>
> > > @@ -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*/)
> >
> > Hmm, I don't love adding a parameter for this case, but I guess a clobber
> > won't work because this is a nop for an object that's already in
> lifetime.
>
> The init in that case is not a clobber, but an empty CONSTRUCTOR with
> the right flags.  Making start_lifetime implied from the
> CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P bit wouldn't be reliable.
> What we could do (but not done in the patch) is use some tree other than
> MODIFY_EXPR/INIT_EXPR with 2+ operands instead of the current MODIFY_EXPR,
> say MODOP_EXPR and let that mean start_lifetime.
>
> > > @@ -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)
> >
> > I'm wondering about the empty_base && start_lifetime case.  empty_base
> means
> > valp is pointing to the initializer for a containing class at this point.
> > But if that object had a value we would have returned early above -- and
> not
> > done anything to start the subobject lifetime.  So this change only
> affects
> > the case where the enclosing class is uninitialized.  With this change,
> we
> > end up at *valp = init below, setting the *valp for the enclosing class
> to a
> > value of the wrong type.
>
> Dropped, completely missed the base part in there.
>
> > So I don't think this change is right.  Which test hits this?  I don't
> see
> > any empty classes in the new tests.
>
> I've added some new tests, one related to non-empty classes
> (within-lifetime8.C) and one to empty classes (within-lifetime9.C).  In the
> latter, some things work, others are xfailed for now (e.g. store to empty
> class member doesn't make that within lifetime right now).
> In any case, I think this is not a problem related primarily to this
> trivial
> union paper, but to the __builtin_is_within_lifetime change.  I can file a
> PR afterwards.
>
> > > @@ -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"))
> >
> > It's surprising that a function named is_std_allocator_allocate also
> returns
> > true for "deallocate".
>
> Most of the spots clearly want to test for both allocate and deallocate.
> Anyway, I've renamed the current one and introduced one for solely
> allocate.
>
> > > +constexpr A fA () { A a; a.a[0] = 1; return a; }
> > > +constexpr A a = fA ();             // { dg-error "is not a constant
> expression" "" { xfail *-*-* } }
> >
> > I don't see any discussion of this xfail above (or a comment here).  Is
> > there a PR?
>
> I've filed PR127235 for this.  It is a preexisting bug (since C++20
> P1331R2 implementation I guess).  Dunno if here it is an error already
> to return the only partially initialized aggregate or only to initialize
> a constexpr var to that.  There is
>   /* The result of a constexpr function must be completely initialized.
>
>      However, in C++20, a constexpr constructor doesn't necessarily have
>      to initialize all the fields, so we don't clear
> CONSTRUCTOR_NO_CLEARING
>      in order to detect reading an uninitialized object in constexpr
> instead
>      of value-initializing it.  (reduced_constant_expression_p is expected
> to
>      take care of clearing the flag.)  */
>   if (TREE_CODE (result) == CONSTRUCTOR
>       && (cxx_dialect < cxx20
>           || !DECL_CONSTRUCTOR_P (fun)))
>     clear_no_implicit_zero (result);
> and fun here is not a constructor, so we happily clear the
> CONSTRUCTOR_NO_CLEARING bits.  Either it is invalid to return something
> like
> that, in that case we should be actually doing VERIFY_CONSTANT somewhere
> where we currently don't do it, or it is not and then we shouldn't ever
> call
> clear_no_implicit_zero for C++ >= 20.
>
> I've also added tests Tomasz wanted.
> The earlier patch passed full bootstrap/regtest on x86_64-linux and
> i686-linux, this one has been just
> 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*'"
> tested so far.
>
> 2026-09-07  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 (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.
>         (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.
>         (cxx_eval_store_expression): Add start_lifetime argument.  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-04 18:32:16.294626850 +0200
> +++ b/gcc/c-family/c-cppbuiltin.cc      2026-09-07 09:37:25.801684787 +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-04 18:32:16.299626789 +0200
> +++ b/gcc/cp/cp-tree.h  2026-09-07 09:59:10.683405499 +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.  */
>
> @@ -5098,6 +5113,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 +7231,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 +9433,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-04 18:32:16.302626753 +0200
> +++ b/gcc/cp/tree.cc    2026-09-07 09:37:25.805084145 +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-04 18:32:16.300626777 +0200
> +++ b/gcc/cp/decl.cc    2026-09-07 10:00:20.930467213 +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 (NULL_TREE, 2);
>
>    /* Guess at the initial static decls size.  */
> --- a/gcc/cp/cp-gimplify.cc     2026-09-04 18:32:16.298626801 +0200
> +++ b/gcc/cp/cp-gimplify.cc     2026-09-07 09:37:25.808522602 +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-04 18:32:16.302626753 +0200
> +++ b/gcc/cp/semantics.cc       2026-09-07 09:50:35.614143955 +0200
> @@ -3300,6 +3300,82 @@ 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:
> +      /* 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 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 ("%qs argument type %qT is not pointer type",
> +                  "__builtin_is_within_lifetime", ptype);
> +         return false;
> +       }
> +      return true;
> +    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 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 ("%qs argument type %qT is not pointer type",
> +                  "__builtin_start_lifetime", ptype);
> +         return false;
> +       }
> +      type = TREE_TYPE (ptype);
> +      if (!complete_type_or_else (type, NULL_TREE))
> +       return false;
> +      if (!CP_AGGREGATE_TYPE_P (type))
> +       {
> +         if (complain & tf_error)
> +           error ("%qs argument type %qT is not a pointer to aggregate
> type",
> +                  "__builtin_start_lifetime", ptype);
> +         return false;
> +       }
> +      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 false;
> +       }
> +      return true;
> +    default:
> +      return true;
> +    }
> +}
> +
>  /* Generate an expression for `FN (ARGS)'.  This may change the
>     contents of ARGS.
>
> @@ -3541,30 +3617,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-04 18:32:16.295626838 +0200
> +++ b/gcc/cp/constexpr.cc       2026-09-07 10:11:08.796843796 +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;
>        }
> @@ -3780,14 +3827,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 +3849,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 +4502,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 +5176,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 +5206,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.  */
> @@ -5168,12 +5245,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 +5270,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 +8301,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 +8321,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 +8662,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 +8765,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
> @@ -10565,7 +10660,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 +10825,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 +12580,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-04
> 18:32:16.303626741 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C   2026-09-07
> 09:37:25.812812860 +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-07
> 09:37:25.812972170 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union3.C       2026-09-07
> 09:37:25.812972170 +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-07
> 09:37:25.813063079 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union4.C       2026-09-07
> 09:37:25.813063079 +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-07
> 09:37:25.813157556 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union5.C       2026-09-07
> 09:37:25.813157556 +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-07
> 09:37:25.813256955 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-07
> 12:14:33.883328793 +0200
> @@ -0,0 +1,83 @@
> +// 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 ()
> +{
> +  E a;
> +  __builtin_start_lifetime (&a.a.b[0]);
>
Add a bool parameter to the function here, and have
   if (x)
      __builtin_start_lifetime (&a.a.b[1]);
Then:
  fE3(false); // error always
  fE4(true); // error prior to c++23

Outside of that, the test and library parts look good to me.

> +  a.a.b[0][0] = 1;
> +  a.a.b[1][1] = 3;
> +  return a;
> +}
> +constexpr E e4 = fE3 ();               // { dg-error "is not a constant
> expression" }
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C     2026-09-04
> 18:32:16.303626741 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C     2026-09-07
> 09:37:25.813567333 +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-07
> 12:41:30.017125773 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C     2026-09-07
> 12:47:03.000000000 +0200
> @@ -0,0 +1,167 @@
> +// 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[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-07
> 13:08:03.554226481 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime9.C     2026-09-07
> 13:25:51.295525649 +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-04
> 18:32:16.303626741 +0200
> +++ b/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C   2026-09-07
> 09:37:25.813921674 +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-04
> 18:32:16.306626704 +0200
> +++ b/libstdc++-v3/include/bits/version.def     2026-09-07
> 09:37:25.814405135 +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-04
> 18:32:16.306626704 +0200
> +++ b/libstdc++-v3/include/bits/version.h       2026-09-07
> 09:37:25.815002416 +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-04
> 18:32:16.306626704 +0200
> +++ b/libstdc++-v3/include/bits/stl_construct.h 2026-09-07
> 09:37:25.815497624 +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-04 18:32:16.310626655 +0200
> +++ b/libstdc++-v3/include/std/memory   2026-09-07 09:37:25.815796815 +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-04 18:32:16.310626655
> +0200
> +++ b/libstdc++-v3/src/c++23/std.cc.in  2026-09-07 09:37:25.816293987
> +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