On Tue, Sep 8, 2026 at 9:14 AM Jakub Jelinek <[email protected]> wrote:
> On Mon, Sep 07, 2026 at 03:06:39PM +0200, Tomasz Kaminski wrote: > > 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. > > I've done that, but that revealed a problem in > reduced_constant_expression_p, in the recursion for ARRAY_TYPE > elements without CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P on the > array CONSTRUCTOR it wasn't passing down union_elemental_subobject. > > Now, the standard wording isn't clear on what happens when the > function template is called on an array where its containing subobject > isn't in lifetime either; I was aware of it when writing the wording; however, the standard does not allow you to invoke start lifetime in such a case, as accessing a member of the object that is not in lifetime is UB, the https://eel.is/c++draft/basic.life#8 says: > the glvalue is used to access the object ([ub:lifetime.outside.glvalue. access] <https://eel.is/c++draft/ub:lifetime.outside.glvalue.access>), or And class member access is access of the object. I believe my view is further corroborated by the fact that for a pointer p pointing to such an object outside its lifetime, accessing member of p->mem is undefined per https://eel.is/c++draft/basic.life#7.2: > the pointer is used to access a non-static data member or call a non-static member function of the object ([ub:lifetime.outside.pointer. member] <https://eel.is/c++draft/ub:lifetime.outside.pointer.member>), or So, in short for start_lifetime(a.arr) the UB happens at a.arr, if a is not within its lifetime. However, this is not rejected by anybody as far as I can tell. > the patch acts for the surrounding subobjects > as a store, so makes all subobjects except for union members and the > object it is called on within lifetime. If something else should happen > (e.g. make it non-constant), the patch will need changing. > I would much more preffer the start_lifetime(a.x) to be non-constant expressions if `a` (or any containing object) is not within its lifetime. I believe this function should start a lifetime of at most a single object. Otherwise it would be unclear, if the enclosing objects should be created (with all members) or only have their lifetimes started. This becomes even more complicated if we consider non-trivial enclosing objects. Also, I want to avoid orphaned in-lifetime objects, when we have some subobject in lifetime, while some of the containing objects are not. This is why the paper hasn't proposed end_lifetime function, as it would naturally lead to such situations, and instead relies on storage reuse to end lieftime of any objects that have they lifetime started. > > Anyway, here is an updated version of the patch, bootstrapped/regtested > on x86_64-linux and i686-linux. > > 2026-09-08 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 20:51:03.750466308 +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. */ > @@ -5143,7 +5220,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 +5246,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 +5271,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 +8302,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 +8322,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 +8663,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 +8766,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 +10661,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 +10826,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 +12581,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 > 20:17:04.883076829 +0200 > @@ -0,0 +1,87 @@ > +// 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]); > + if (x) > + __builtin_start_lifetime (&a.a.b[1]); > + a.a.b[0][0] = 1; > + a.a.b[1][1] = 3; > + return a; > +} > +constexpr E e4 = fE3 (false); // { dg-error "is not a constant > expression" } > +constexpr E e5 = fE3 (true); // { dg-error "is not a constant > expression" "" { target c++23_down } } > --- 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 > >
