Hi! Thanks for all the review comments.
On Wed, Sep 09, 2026 at 03:15:54PM -0400, Jason Merrill wrote: > > > + case CP_BUILT_IN_START_LIFETIME: > > > *expr_p = void_node; > > > > Is it safe to drop this when the argument has side-effects? Changed that to fold_convert to void_type_node. For uses in std::start_lifetime it shouldn't make a difference, but when people use the builtin directly it matters. > > seems to duplicate the previous case almost verbatim. > > Indeed, and perhaps we could use DECL_NAME (fn) instead of extra string > literals in the diagnostics? Shared what could be shared and used DECL_NAME (fn), both here and in constexpr.cc too. > > > + type = TREE_TYPE (ptype); > > > + if (!complete_type_or_else (type, NULL_TREE)) > > > > This emits a diagnostic unconditionally, should it be > > complete_type_or_maybe_complain? Elsewhere this function > > checks complain first. > Good point. Done too. 2026-09-09 Jakub Jelinek <[email protected]> P3726R2 PR c++/125845 gcc/c-family/ * c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine __cpp_trivial_union to 202603L. gcc/cp/ * cp-tree.h: Implement C++26 P3726R2 - Adjustments to Union Lifetime Rules. Document some generic flags used in C++ specific way on some trees in the toplevel comment. (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P): Define. (enum cp_built_in_function): Add CP_BUILT_IN_START_LIFETIME. (reduced_constant_expression_p): Add a bool argument defaulted to false. * tree.cc (builtin_valid_in_constant_expr_p): Handle CP_BUILT_IN_START_LIFETIME. * decl.cc (cxx_init_decl_processing): Create __builtin_start_lifetime decl. * cp-gimplify.cc (cp_gimplify_expr): Handle CP_BUILT_IN_START_LIFETIME. * semantics.cc (check_frontend_builtin): New function. (finish_call_expr): Use it. * constexpr.cc (is_within_lifetime): New function. (cxx_eval_is_within_lifetime): Use it. Add fun argument and pass it to that function. (cxx_eval_start_lifetime): New function. (cxx_eval_builtin_function_call): Handle CP_BUILT_IN_START_LIFETIME. Pass fun to cxx_eval_is_within_lifetime. (is_std_allocator_allocate): Only return true for std::allocator<T>::allocate and not for std::allocator<T>::deallocate. (is_std_allocator_allocate_deallocate): New functions. (cxx_eval_call_expression): Use is_std_allocator_allocate_deallocate rather than is_std_allocator_allocate. (reduced_constant_expression_p): Add union_elemental_subobject argument and propagate it to recursive calls. If set, ignore for C++26 holes in array initializers which aren't within lifetime. (modifying_const_object_p): Change != MODIFY_EXPR test to == INIT_EXPR. (cxx_eval_store_expression): Handle MODOP_EXPR next to INIT_EXPR and MODIFY_EXPR, meaning start_lifetime. Don't preevaluate init in that case, return void_node early if *valp is already non-NULL/void_node and within lifetime. (cxx_eval_constant_expression): For std::allocator<T>::allocate start lifetime of the created array but not of its subobjects. Use is_std_allocator_allocate_deallocate rather than is_std_allocator_allocate where appropriate. (potential_constant_expression_1): Likewise. gcc/testsuite/ * g++.dg/cpp26/feat-cxx26.C: Adjust __cpp_trivial_union checking. * g++.dg/cpp26/trivial-union3.C: New test. * g++.dg/cpp26/trivial-union4.C: New test. * g++.dg/cpp26/trivial-union5.C: New test. * g++.dg/cpp26/trivial-union6.C: New test. * g++.dg/cpp26/within-lifetime3.C: Remove #if 0 and #endif. * g++.dg/cpp26/within-lifetime8.C: New test. * g++.dg/cpp26/within-lifetime9.C: New test. * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking. libstdc++-v3/ * include/bits/version.def (start_lifetime): New. * include/bits/version.h: Regenerate. * include/bits/stl_construct.h (std::start_lifetime): New function template. * include/std/memory: Define __glibcxx_want_start_lifetime before including bits/version.h. * src/c++23/std.cc.in: Add export std::start_lifetime. --- a/gcc/c-family/c-cppbuiltin.cc 2026-09-09 17:03:29.365511237 +0200 +++ b/gcc/c-family/c-cppbuiltin.cc 2026-09-09 17:08:09.016033509 +0200 @@ -1124,7 +1124,7 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_impl_reflection=202603L"); else cpp_warn (pfile, "__cpp_impl_reflection"); - cpp_define (pfile, "__cpp_trivial_union=202502L"); + cpp_define (pfile, "__cpp_trivial_union=202603L"); } if (cxx_dialect > cxx26) { --- a/gcc/cp/cp-tree.h 2026-09-09 17:03:29.376511095 +0200 +++ b/gcc/cp/cp-tree.h 2026-09-09 17:08:09.016805380 +0200 @@ -644,7 +644,22 @@ extern GTY(()) tree cp_global_trees[CPTI Temporarily, it may be set to a TREE_LIST whose TREE_VALUE is the virtual function this one overrides, and whose TREE_CHAIN is - the old DECL_VINDEX. */ + the old DECL_VINDEX. + + TREE_PRIVATE + For CONSTRUCTOR, this is CONSTRUCTOR_IS_PAREN_INIT. + + TREE_PROTECTED + For CONSTRUCTOR, this is CONSTRUCTOR_BRACES_ELIDED_P. + For IDENTIFIER, this is IDENTIFIER_LAMBDA_P. + For AGGR_INIT_EXPR, this is AGGR_INIT_FROM_THUNK_P. + For ADDR_EXPR, this is ADDR_EXPR_DENOTES_CALL_P. + + TREE_STATIC + For AGGR_INIT_EXPR, this is AGGR_INIT_EXPR_MUST_TAIL. + + TREE_DEPRECATED + For CONSTRUCTOR, this is CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P. */ /* Language-specific tree checkers. */ @@ -5099,6 +5114,10 @@ get_vec_init_expr (tree t) #define CONSTRUCTOR_BRACES_ELIDED_P(NODE) \ (CONSTRUCTOR_CHECK (NODE)->base.protected_flag) +/* True if omitted fields are considered to be not within lifetime. */ +#define CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P(NODE) \ + (CONSTRUCTOR_CHECK (NODE)->base.deprecated_flag) + /* True if NODE represents a conversion for direct-initialization in a template. Set by perform_implicit_conversion_flags. */ #define IMPLICIT_CONV_EXPR_DIRECT_INIT(NODE) \ @@ -7231,6 +7250,7 @@ enum cp_built_in_function { CP_BUILT_IN_CURRENT_EXCEPTION, CP_BUILT_IN_UNCAUGHT_EXCEPTIONS, CP_BUILT_IN_IS_WITHIN_LIFETIME, + CP_BUILT_IN_START_LIFETIME, CP_BUILT_IN_LAST }; @@ -9432,7 +9452,7 @@ extern tree fold_non_dependent_init (tr bool = false, tree = NULL_TREE); extern tree fold_simple (tree); extern tree fold_to_constant (tree); -extern bool reduced_constant_expression_p (tree, tree = NULL_TREE); +extern bool reduced_constant_expression_p (tree, tree = NULL_TREE, bool = false); extern bool is_instantiation_of_constexpr (tree); extern bool var_in_constexpr_fn (tree); extern bool var_in_maybe_constexpr_fn (tree); --- a/gcc/cp/tree.cc 2026-09-09 17:03:29.394510863 +0200 +++ b/gcc/cp/tree.cc 2026-09-09 17:08:09.017716173 +0200 @@ -579,6 +579,7 @@ builtin_valid_in_constant_expr_p (const_ case CP_BUILT_IN_CURRENT_EXCEPTION: case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS: case CP_BUILT_IN_IS_WITHIN_LIFETIME: + case CP_BUILT_IN_START_LIFETIME: return true; default: break; --- a/gcc/cp/decl.cc 2026-09-09 17:03:29.377511082 +0200 +++ b/gcc/cp/decl.cc 2026-09-09 17:08:09.018991479 +0200 @@ -5679,6 +5679,13 @@ cxx_init_decl_processing (void) set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); SET_DECL_IMMEDIATE_FUNCTION_P (decl); + tree void_vaftype = build_varargs_function_type_list (void_type_node, + NULL_TREE); + decl = add_builtin_function ("__builtin_start_lifetime", + void_vaftype, CP_BUILT_IN_START_LIFETIME, + BUILT_IN_FRONTEND, NULL, NULL_TREE); + set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); + integer_two_node = build_int_cst (integer_type_node, 2); /* Guess at the initial static decls size. */ --- a/gcc/cp/cp-gimplify.cc 2026-09-09 17:03:29.375511108 +0200 +++ b/gcc/cp/cp-gimplify.cc 2026-09-09 22:31:38.233689704 +0200 @@ -1042,6 +1042,10 @@ cp_gimplify_expr (tree *expr_p, gimple_s case CP_BUILT_IN_CONSTEXPR_DIAG: *expr_p = void_node; break; + case CP_BUILT_IN_START_LIFETIME: + *expr_p = fold_convert (void_type_node, + CALL_EXPR_ARG (*expr_p, 0)); + break; default: break; } --- a/gcc/cp/semantics.cc 2026-09-09 17:03:29.386510966 +0200 +++ b/gcc/cp/semantics.cc 2026-09-09 22:41:50.076627312 +0200 @@ -3300,6 +3300,63 @@ perform_koenig_lookup (cp_expr fn_expr, return cp_expr (fn, loc); } +/* Analyze call to a front-end builtin FN with arguments *ARGS. Return true + if the call is correct, false if erroneous. */ + +static bool +check_frontend_builtin (tree fn, vec<tree, va_gc> **args, + tsubst_flags_t complain) +{ + tree arg, ptype, type; + switch (DECL_UNCHECKED_FUNCTION_CODE (fn)) + { + case CP_BUILT_IN_IS_WITHIN_LIFETIME: + case CP_BUILT_IN_START_LIFETIME: + /* Unless users call the builtin directly, the following 2 checks + should be ensured from std::is_within_lifetime or + std::start_lifetime template. */ + if (vec_safe_length (*args) != 1) + { + if (complain & tf_error) + error ("%qE needs a single argument", DECL_NAME (fn)); + return false; + } + arg = (**args)[0]; + if (error_operand_p (arg)) + return false; + ptype = TREE_TYPE (arg); + if (!POINTER_TYPE_P (ptype)) + { + if (complain & tf_error) + error ("%qE argument type %qT is not pointer type", + DECL_NAME (fn), ptype); + return false; + } + if (DECL_UNCHECKED_FUNCTION_CODE (fn) == CP_BUILT_IN_IS_WITHIN_LIFETIME) + return true; + type = TREE_TYPE (ptype); + if (!complete_type_or_maybe_complain (type, NULL_TREE, complain)) + return false; + if (!CP_AGGREGATE_TYPE_P (type)) + { + if (complain & tf_error) + error ("%qE argument type %qT is not a pointer to aggregate type", + DECL_NAME (fn), ptype); + return false; + } + if (!implicit_lifetime_type_p (type)) + { + if (complain & tf_error) + error ("%qE argument type %qT is not a pointer to " + "implicit-lifetime type", DECL_NAME (fn), ptype); + return false; + } + return true; + default: + return true; + } +} + /* Generate an expression for `FN (ARGS)'. This may change the contents of ARGS. @@ -3541,30 +3598,9 @@ finish_call_expr (tree fn, vec<tree, va_ } if (TREE_CODE (fn) == FUNCTION_DECL - && fndecl_built_in_p (fn, CP_BUILT_IN_IS_WITHIN_LIFETIME, - BUILT_IN_FRONTEND)) - { - /* Unless users call the builtin directly, the following 2 checks - should be ensured from std::is_within_lifetime template. */ - if (vec_safe_length (*args) != 1) - { - if (complain & tf_error) - error ("%<__builtin_is_within_lifetime%> needs a single " - "argument"); - return error_mark_node; - } - tree arg = (**args)[0]; - if (error_operand_p (arg)) - return error_mark_node; - tree ptype = TREE_TYPE (arg); - if (!POINTER_TYPE_P (ptype)) - { - if (complain & tf_error) - error ("%<__builtin_is_within_lifetime%> argument type " - "%qT is not pointer type", ptype); - return error_mark_node; - } - } + && fndecl_built_in_p (fn, BUILT_IN_FRONTEND) + && !check_frontend_builtin (fn, args, complain)) + return error_mark_node; /* A call to a namespace-scope function. */ result = build_new_function_call (fn, args, orig_complain); --- a/gcc/cp/constexpr.cc 2026-09-09 17:03:29.374511120 +0200 +++ b/gcc/cp/constexpr.cc 2026-09-09 22:41:08.379178946 +0200 @@ -2541,62 +2541,14 @@ cxx_eval_constexpr_diag (const constexpr static tree eval_and_check_array_index (const constexpr_ctx *, tree, bool, bool *, bool *, tree *); -/* Attempt to evaluate T which represents a call to - __builtin_is_within_lifetime. */ +/* Helper function for cxx_eval_is_within_lifetime and + cxx_eval_start_lifetime. Check if ARG is within lifetime. */ static tree -cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t, - bool *non_constant_p, bool *overflow_p, - tree *jump_target) +is_within_lifetime (location_t loc, const constexpr_ctx *ctx, tree t, tree fun, + tree arg, bool *non_constant_p, bool *overflow_p, + tree *jump_target) { - location_t loc = EXPR_LOCATION (t); - tree arg = CALL_EXPR_ARG (t, 0); - arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue, - non_constant_p, overflow_p, - jump_target); - if (*jump_target) - return NULL_TREE; - if (*non_constant_p) - return t; - /* Need to strip casts to pointers to void, but should preserve - pointer casts within the innermost pointer to void cast if - any, so that e.g. pointers to heap allocations are handled - correctly. */ - tree p = arg; - while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR) - { - if (!TYPE_PTR_P (TREE_TYPE (p))) - break; - if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p)))) - arg = TREE_OPERAND (p, 0); - p = TREE_OPERAND (p, 0); - } - if (integer_zerop (arg)) - { - if (!ctx->quiet) - error_at (loc, "%qs called with a null pointer", - "__builtin_is_within_lifetime"); - *non_constant_p = true; - return t; - } - if (POINTER_TYPE_P (TREE_TYPE (arg)) - && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE - || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE)) - { - if (!ctx->quiet) - error_at (loc, "%qs called with pointer to function", - "__builtin_is_within_lifetime"); - *non_constant_p = true; - return t; - } - arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg); - arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue, - non_constant_p, overflow_p, - jump_target); - if (*jump_target) - return NULL_TREE; - if (*non_constant_p) - return t; auto_vec <tree, 4> refs; tree obj; for (obj = arg; ; obj = TREE_OPERAND (obj, 0)) @@ -2635,22 +2587,19 @@ cxx_eval_is_within_lifetime (const const auto_diagnostic_group d; if (DECL_P (obj) && DECL_NAME (obj) == heap_deleted_identifier) { - error_at (loc, "%qs on allocated storage after deallocation " - "is not a constant expression", - "__builtin_is_within_lifetime"); + error_at (loc, "%qE on allocated storage after deallocation " + "is not a constant expression", DECL_NAME (fun)); inform (DECL_SOURCE_LOCATION (obj), "allocated here"); } else if (DECL_P (obj) && ctx->global->is_outside_lifetime (obj)) { - error_at (loc, "%qs on %qE after its storage has been released " - "is not a constant expression", - "__builtin_is_within_lifetime", obj); + error_at (loc, "%qE on %qE after its storage has been released " + "is not a constant expression", DECL_NAME (fun), obj); inform (DECL_SOURCE_LOCATION (obj), "declared here"); } else - error_at (loc, "%qs on %qE from outside current evaluation " - "is not a constant expression", - "__builtin_is_within_lifetime", obj); + error_at (loc, "%qE on %qE from outside current evaluation " + "is not a constant expression", DECL_NAME (fun), obj); } *non_constant_p = true; return t; @@ -2687,9 +2636,8 @@ cxx_eval_is_within_lifetime (const const if (check_mutable && DECL_MUTABLE_P (TREE_OPERAND (ref, 1))) { if (!ctx->quiet) - error_at (loc, "%qs on %<mutable%> sub-object %qD", - "__builtin_is_within_lifetime", - TREE_OPERAND (ref, 1)); + error_at (loc, "%qE on %<mutable%> sub-object %qD", + DECL_NAME (fun), TREE_OPERAND (ref, 1)); *non_constant_p = true; return t; } @@ -2748,7 +2696,11 @@ cxx_eval_is_within_lifetime (const const if (ref == NULL_TREE) continue; if (CONSTRUCTOR_NO_CLEARING (val)) - val = val_uninit; + { + if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val)) + return boolean_false_node; + val = val_uninit; + } else val = val_zero_init; continue; @@ -2775,7 +2727,11 @@ cxx_eval_is_within_lifetime (const const continue; } if (CONSTRUCTOR_NO_CLEARING (val)) - val = val_uninit; + { + if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val)) + return boolean_false_node; + val = val_uninit; + } else val = val_zero_init; continue; @@ -2785,6 +2741,135 @@ cxx_eval_is_within_lifetime (const const return boolean_true_node; } +/* Attempt to evaluate T which represents a call to + __builtin_is_within_lifetime. */ + +static tree +cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t, tree fun, + bool *non_constant_p, bool *overflow_p, + tree *jump_target) +{ + location_t loc = EXPR_LOCATION (t); + tree arg = CALL_EXPR_ARG (t, 0); + arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + /* Need to strip casts to pointers to void, but should preserve + pointer casts within the innermost pointer to void cast if + any, so that e.g. pointers to heap allocations are handled + correctly. */ + tree p = arg; + while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR) + { + if (!TYPE_PTR_P (TREE_TYPE (p))) + break; + if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p)))) + arg = TREE_OPERAND (p, 0); + p = TREE_OPERAND (p, 0); + } + if (integer_zerop (arg)) + { + if (!ctx->quiet) + error_at (loc, "%qE called with a null pointer", + DECL_NAME (fun)); + *non_constant_p = true; + return t; + } + if (POINTER_TYPE_P (TREE_TYPE (arg)) + && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE + || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE)) + { + if (!ctx->quiet) + error_at (loc, "%qE called with pointer to function", + DECL_NAME (fun)); + *non_constant_p = true; + return t; + } + arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg); + arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + return is_within_lifetime (loc, ctx, t, fun, arg, + non_constant_p, overflow_p, jump_target); +} + +static tree cxx_eval_store_expression (const constexpr_ctx *, tree, + value_cat, bool *, bool *, + tree *); + +/* Attempt to evaluate T which represents a call to + __builtin_start_lifetime. */ + +static tree +cxx_eval_start_lifetime (const constexpr_ctx *ctx, tree t, tree fun, + bool *non_constant_p, bool *overflow_p, + tree *jump_target) +{ + location_t loc = EXPR_LOCATION (t); + tree arg = CALL_EXPR_ARG (t, 0), within_lifetime; + arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg); + arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + switch (TREE_CODE (arg)) + { + case COMPONENT_REF: + case ARRAY_REF: + case REALPART_EXPR: + case IMAGPART_EXPR: + within_lifetime + = is_within_lifetime (loc, ctx, t, fun, TREE_OPERAND (arg, 0), + non_constant_p, overflow_p, jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + if (within_lifetime != boolean_true_node) + { + if (!ctx->quiet) + error_at (loc, "%qE with containing object not within lifetime", + DECL_NAME (fun)); + *non_constant_p = true; + return t; + } + break; + default: + break; + } + tree ctor = build_constructor (TREE_TYPE (arg), NULL); + CONSTRUCTOR_NO_CLEARING (ctor) = 1; + CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1; + tree modopexpr + = build3_loc (loc, MODOP_EXPR, TREE_TYPE (arg), arg, ctor, NULL_TREE); + arg = cxx_eval_store_expression (ctx, modopexpr, vc_discard, non_constant_p, + overflow_p, jump_target); + ggc_free (modopexpr); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + return void_node; +} + /* Attempt to evaluate T which represents a call to a builtin function. We assume here that all builtin functions evaluate to scalar types represented by _CST nodes. */ @@ -2859,8 +2944,12 @@ cxx_eval_builtin_function_call (const co jump_target); case CP_BUILT_IN_IS_WITHIN_LIFETIME: - return cxx_eval_is_within_lifetime (ctx, t, non_constant_p, overflow_p, - jump_target); + return cxx_eval_is_within_lifetime (ctx, t, fun, non_constant_p, + overflow_p, jump_target); + + case CP_BUILT_IN_START_LIFETIME: + return cxx_eval_start_lifetime (ctx, t, fun, non_constant_p, + overflow_p, jump_target); default: break; @@ -3780,14 +3869,13 @@ is_std_allocator (tree ctx) return is_std_class (ctx, "allocator"); } -/* Return true if FNDECL is std::allocator<T>::{,de}allocate. */ +/* Return true if FNDECL is std::allocator<T>::allocate. */ static bool is_std_allocator_allocate (tree fndecl) { tree name = DECL_NAME (fndecl); - if (name == NULL_TREE - || !(id_equal (name, "allocate") || id_equal (name, "deallocate"))) + if (name == NULL_TREE || !id_equal (name, "allocate")) return false; return is_std_allocator (DECL_CONTEXT (fndecl)); @@ -3803,6 +3891,29 @@ is_std_allocator_allocate (const constex && is_std_allocator_allocate (call->fundef->decl)); } +/* Return true if FNDECL is std::allocator<T>::{,de}allocate. */ + +static bool +is_std_allocator_allocate_deallocate (tree fndecl) +{ + tree name = DECL_NAME (fndecl); + if (name == NULL_TREE + || !(id_equal (name, "allocate") || id_equal (name, "deallocate"))) + return false; + + return is_std_allocator (DECL_CONTEXT (fndecl)); +} + +/* Overload for the above taking constexpr_call*. */ + +static inline bool +is_std_allocator_allocate_deallocate (const constexpr_call *call) +{ + return (call + && call->fundef + && is_std_allocator_allocate_deallocate (call->fundef->decl)); +} + /* Return true if FNDECL is std::source_location::current. */ static inline bool @@ -4433,7 +4544,7 @@ cxx_eval_call_expression (const constexp if (TREE_CODE (t) == CALL_EXPR && cxx_replaceable_global_alloc_fn (fun) && (CALL_FROM_NEW_OR_DELETE_P (t) - || is_std_allocator_allocate (ctx->call))) + || is_std_allocator_allocate_deallocate (ctx->call))) { const bool new_op_p = IDENTIFIER_NEW_OP_P (DECL_NAME (fun)); const int nargs = call_expr_nargs (t); @@ -5107,10 +5218,13 @@ cxx_eval_call_expression (const constexp initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will be cleared. If called recursively on a FIELD_DECL's CONSTRUCTOR, SZ is DECL_SIZE of the FIELD_DECL, otherwise NULL. + UNION_ELEMENTAL_SUBOBJECT is true when recursing on union elemental + subobjects. FIXME speed this up, it's taking 16% of compile time on sieve testcase. */ bool -reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */) +reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */, + bool union_elemental_subobject /* = false */) { if (t == NULL_TREE) return false; @@ -5134,7 +5248,12 @@ reduced_constant_expression_p (tree t, t return false; if (CONSTRUCTOR_NO_CLEARING (t)) { - if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) + if (union_elemental_subobject + && cxx_dialect >= cxx26 + && TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE + && CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (t)) + field = NULL_TREE; + else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) { /* There must be a valid constant initializer at every array index. */ @@ -5143,7 +5262,8 @@ reduced_constant_expression_p (tree t, t tree cursor = min; for (auto &e: CONSTRUCTOR_ELTS (t)) { - if (!reduced_constant_expression_p (e.value)) + if (!reduced_constant_expression_p + (e.value, NULL_TREE, union_elemental_subobject)) return false; if (array_index_cmp (cursor, e.index) != 0) return false; @@ -5168,12 +5288,22 @@ reduced_constant_expression_p (tree t, t active member has no constituent values, so all constituent values are constant. */ field = NULL_TREE; + union_elemental_subobject = true; } else - field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t))); + { + field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t))); + union_elemental_subobject = false; + } } else - field = NULL_TREE; + { + field = NULL_TREE; + if (TREE_CODE (TREE_TYPE (t)) == UNION_TYPE) + union_elemental_subobject = true; + else if (TREE_CODE (TREE_TYPE (t)) != ARRAY_TYPE) + union_elemental_subobject = false; + } for (auto &e: CONSTRUCTOR_ELTS (t)) { /* If VAL is null, we're in the middle of initializing this @@ -5183,7 +5313,8 @@ reduced_constant_expression_p (tree t, t && (TREE_CODE (e.index) == FIELD_DECL)) ? DECL_SIZE (e.index) - : NULL_TREE)) + : NULL_TREE, + union_elemental_subobject)) return false; /* We want to remove initializers for empty fields in a struct to avoid confusing output_constructor. */ @@ -8186,7 +8317,7 @@ static bool modifying_const_object_p (tree_code code, tree obj, bool mutable_p) { /* If this is initialization, there's no problem. */ - if (code != MODIFY_EXPR) + if (code == INIT_EXPR) return false; /* [basic.type.qualifier] "A const object is an object of type @@ -8213,7 +8344,9 @@ modifying_const_object_p (tree_code code return false; } -/* Evaluate an INIT_EXPR or MODIFY_EXPR. */ +/* Evaluate an INIT_EXPR or MODIFY_EXPR or MODOP_EXPR. MODOP_EXPR means + we are evaluating __builtin_start_lifetime (init is in the second + operand rather than third, like for INIT_EXPR and MODIFY_EXPR). */ static tree cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, @@ -8231,8 +8364,8 @@ cxx_eval_store_expression (const constex maybe_simplify_trivial_copy (target, init); tree type = TREE_TYPE (target); - bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR; - if (preeval && !TREE_CLOBBER_P (init)) + bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) != INIT_EXPR; + if (preeval && !TREE_CLOBBER_P (init) && TREE_CODE (t) != MODOP_EXPR) { /* Ignore var = .DEFERRED_INIT (); for now, until PR121965 is fixed. */ if (flag_auto_var_init > AUTO_INIT_UNINITIALIZED @@ -8573,12 +8706,13 @@ cxx_eval_store_expression (const constex index); *non_constant_p = true; } - else if (!is_access_expr - || (TREE_CLOBBER_P (init) - && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) - || (TREE_CODE (t) == MODIFY_EXPR - && CLASS_TYPE_P (inner) - && !type_has_non_deleted_trivial_default_ctor (inner))) + else if ((!is_access_expr + || (TREE_CLOBBER_P (init) + && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) + || (TREE_CODE (t) != INIT_EXPR + && CLASS_TYPE_P (inner) + && !type_has_non_deleted_trivial_default_ctor (inner))) + && TREE_CODE (t) != MODOP_EXPR) { /* Diagnose changing active union member after initialization without a valid member access expression, as described in @@ -8675,6 +8809,11 @@ cxx_eval_store_expression (const constex empty_base = true; } + /* For __builtin_start_lifetime, if it is already within lifetime, + don't do anything. */ + if (TREE_CODE (t) == MODOP_EXPR && *valp != NULL_TREE && *valp != void_node) + return void_node; + /* Detect modifying a constant object in constexpr evaluation. We have found a const object that is being modified. Figure out if we need to issue an error. Consider @@ -10565,7 +10704,7 @@ cxx_eval_constant_expression (const cons std::source_location::current, we permit casting from void* because that is compiler-generated code. */ && !is_std_construct_at (ctx->call) - && !is_std_allocator_allocate (ctx->call) + && !is_std_allocator_allocate_deallocate (ctx->call) && !is_std_source_location_current (ctx->call)) { /* Likewise, don't error when casting from void* when OP is @@ -10730,6 +10869,16 @@ cxx_eval_constant_expression (const cons TREE_TYPE (var) = new_type; TREE_TYPE (TREE_OPERAND (op, 0)) = build_pointer_type (TREE_TYPE (var)); + if (is_std_allocator_allocate (ctx->call)) + { + /* [allocator.members]/5: This function starts the lifetime + of the array object, but not that of any of the array + elements. */ + tree ctor = build_constructor (new_type, NULL); + CONSTRUCTOR_NO_CLEARING (ctor) = 1; + CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1; + ctx->global->put_value (var, ctor); + } } /* This can happen for std::meta::info(^^int) where the cast has no @@ -12475,7 +12624,7 @@ potential_constant_expression_1 (tree t, || TREE_CODE (t) != CALL_EXPR || (!CALL_FROM_NEW_OR_DELETE_P (t) && (current_function_decl == NULL_TREE - || !is_std_allocator_allocate + || !is_std_allocator_allocate_deallocate (current_function_decl)))) /* Allow placement new in std::construct_at. */ && (!cxx_placement_new_fn (fun) --- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-09-09 17:03:29.405510721 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-09-09 17:08:09.023187822 +0200 @@ -655,8 +655,8 @@ #ifndef __cpp_trivial_union # error "__cpp_trivial_union" -#elif __cpp_trivial_union != 202502 -# error "__cpp_trivial_union != 202502" +#elif __cpp_trivial_union != 202603 +# error "__cpp_trivial_union != 202603" #endif // C++26 attributes: --- a/gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-09-09 17:08:09.023313841 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-09-09 17:08:09.023313841 +0200 @@ -0,0 +1,28 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do compile { target c++26 } } + +#include <memory> +#include <string> + +template <typename T, size_t N> +struct FixedVector { + union { T storage[N]; }; + size_t size = 0; + + constexpr FixedVector () { std::start_lifetime (storage); } + + constexpr ~FixedVector() { std::destroy(storage, storage + size); } + + constexpr void push_back(T const& v) { ::new (storage + size) T(v); ++size; } +}; + +constexpr size_t +silly_test () +{ + FixedVector <std::string, 3> v; + v.push_back ("some sufficiently longer string"); + return v.size; +} + +static_assert (silly_test () == 1); --- a/gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-09-09 17:08:09.023439796 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-09-09 22:45:12.019955720 +0200 @@ -0,0 +1,74 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do run { target c++26 } } + +namespace std { + template <class T> + constexpr void + start_lifetime (T &r) noexcept + { + __builtin_start_lifetime (__builtin_addressof (r)); + } +} + +struct S { int a; }; +union U { int a; S b[3]; }; +union V { int a; U b[2]; }; +struct W { S a[2]; }; + +consteval +{ + S s; + s.a = 42; + std::start_lifetime (s); + if (s.a != 42) + throw 1; + U u; + u.b[0].a = 42; + std::start_lifetime (u.b); + std::start_lifetime (u.b[0]); + std::start_lifetime (u.b[2]); + u.a = 42; + std::start_lifetime (u.b); + std::start_lifetime (u.b[0]); + std::start_lifetime (u.b[1]); + V v; + v.a = 42; + std::start_lifetime (v.b); + std::start_lifetime (v.b[1]); + v.b[1].a = 42; + std::start_lifetime (v.b[1].b); + std::start_lifetime (v.b[1].b[1]); + v.b[1].b[1].a = 43; + std::start_lifetime (v.b); + std::start_lifetime (v.b[1].b[1]); + if (v.b[1].b[1].a != 43) + throw 2; + const S cs = { 42 }; + std::start_lifetime (cs); + if (cs.a != 42) + throw 3; + const U cu = { .b = { { 41 }, { 42 }, { 43 } } }; + std::start_lifetime (cu.b); + std::start_lifetime (cu.b[0]); + std::start_lifetime (cu.b[2]); + if (cu.b[2].a != 43) + throw 4; + const V cv = { .b = { { .b = { { 44 } } } } }; + std::start_lifetime (cv.b); + std::start_lifetime (cv.b[0]); + std::start_lifetime (cv.b[0].b); + std::start_lifetime (cv.b[0].b[0]); + if (cv.b[0].b[0].a != 44) + throw 5; +} + +int +main () +{ + W w; + int i = 0; + __builtin_start_lifetime (&w.a[++i]); + if (i != 1) + __builtin_abort (); +} --- a/gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-09-09 17:08:09.023540149 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-09-09 17:08:09.023540149 +0200 @@ -0,0 +1,37 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do compile { target c++26 } } + +struct S { int a; }; +S a = { 5 }; +struct T { constexpr T () : a (42) {} int a; }; +struct R { int a; constexpr ~R () {} }; +union U { int a; S b[2]; }; + +consteval { int a = 5; __builtin_start_lifetime (&a); } // { dg-error "'__builtin_start_lifetime' argument type 'int\\\*' is not a pointer to aggregate type" } +consteval { __builtin_start_lifetime (&a); }; // { dg-error "modification of 'a' from outside current evaluation is not a constant expression" } +consteval { __builtin_start_lifetime (); }; // { dg-error "'__builtin_start_lifetime' needs a single argument" } +consteval { S a = { 6 }; __builtin_start_lifetime (&a, &a); }; // { dg-error "'__builtin_start_lifetime' needs a single argument" } +consteval { __builtin_start_lifetime (42); }; // { dg-error "'__builtin_start_lifetime' argument type 'int' is not pointer type" } +consteval { T t; __builtin_start_lifetime (&t); } // { dg-error "'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer to aggregate type" } +consteval { R r; __builtin_start_lifetime (&r); } // { dg-error "'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer to implicit-lifetime type" } +consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.a); } // { dg-error "'__builtin_start_lifetime' argument type 'const int\\\*' is not a pointer to aggregate type" } +consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.b); } // { dg-error "modifying a const object 'u.U::b' is not allowed in a constant expression" } + +void +foo () +{ + S s; + __builtin_start_lifetime (&s); + int a = 5; + __builtin_start_lifetime (&a); // { dg-error "'__builtin_start_lifetime' argument type 'int\\\*' is not a pointer to aggregate type" } + __builtin_start_lifetime (&::a); + __builtin_start_lifetime (); // { dg-error "'__builtin_start_lifetime' needs a single argument" } + S b = { 6 }; + __builtin_start_lifetime (&b, &b); // { dg-error "'__builtin_start_lifetime' needs a single argument" } + __builtin_start_lifetime (42); // { dg-error "'__builtin_start_lifetime' argument type 'int' is not pointer type" } + T t; + __builtin_start_lifetime (&t); // { dg-error "'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer to aggregate type" } + R r; + __builtin_start_lifetime (&r); // { dg-error "'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer to implicit-lifetime type" } +} --- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-09 17:08:09.023638485 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-09 17:10:56.651679279 +0200 @@ -0,0 +1,167 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do compile { target c++20 } } + +struct A { int a[2]; }; +union B { int a; int b[2]; }; +union C { int a; int b[2][2]; }; +struct D { B a; }; +struct E { C a; }; +union F { int a; A b; }; +union G { int a; A b[2]; }; + +constexpr A fA () { A a; a.a[0] = 1; return a; } +constexpr A a = fA (); // { dg-error "is not a constant expression" "" { xfail *-*-* } } +constexpr B fB () { B a; a.b[0] = 1; return a; } +constexpr B b = fB (); // { dg-error "is not a constant expression" } +constexpr C fC () { C a; a.b[0][0] = 1; a.b[1][0] = 2; a.b[1][1] = 3; return a; } +constexpr C c = fC (); // { dg-error "is not a constant expression" } +constexpr D fD () { D a; a.a.b[0] = 1; return a; } +constexpr D d = fD (); // { dg-error "is not a constant expression" } +constexpr E fE () { E a; a.a.b[0][0] = 1; a.a.b[1][0] = 2; a.a.b[1][1] = 3; return a; } +constexpr E e = fE (); // { dg-error "is not a constant expression" } +constexpr F fF () { F a; a.b.a[0] = 1; return a; } +constexpr F f = fF (); // { dg-error "is not a constant expression" } +constexpr G fG () { G a; a.b[0].a[0] = 1; return a; } +constexpr G g = fG (); // { dg-error "is not a constant expression" } +constexpr B +fB2 () +{ + B a; + __builtin_start_lifetime (&a.b); + a.b[0] = 1; + return a; +} +constexpr B b2 = fB2 (); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr C +fC2 (bool x) +{ + C a; + __builtin_start_lifetime (&a.b); + __builtin_start_lifetime (&a.b[0]); + if (x) + __builtin_start_lifetime (&a.b[1]); + a.b[0][0] = 1; + a.b[1][0] = 2; + a.b[1][1] = 3; + return a; +} +constexpr C c2 = fC2 (false); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr C c3 = fC2 (true); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr D +fD2 () +{ + D a; + __builtin_start_lifetime (&a.a.b); + a.a.b[0] = 1; + return a; +} +constexpr D d2 = fD2 (); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr E +fE2 (bool x) +{ + E a; + __builtin_start_lifetime (&a.a.b); + __builtin_start_lifetime (&a.a.b[0]); + if (x) + __builtin_start_lifetime (&a.a.b[1]); + a.a.b[0][0] = 1; + a.a.b[1][0] = 2; + a.a.b[1][1] = 3; + return a; +} +constexpr E e2 = fE2 (false); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr E e3 = fE2 (true); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr E +fE3 (bool x) +{ + E a; + __builtin_start_lifetime (&a.a.b[0]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + if (x) + __builtin_start_lifetime (&a.a.b[1]); + a.a.b[0][0] = 1; + a.a.b[1][1] = 3; + return a; +} +constexpr E e4 = fE3 (false); +constexpr E e5 = fE3 (true); +union H { int a; A b[2][2]; }; +union I { int a; H b[2]; }; +constexpr bool +fH (int x) +{ + G a; + I b; + switch (x) + { + case 0: + __builtin_start_lifetime (&a.b); + break; + case 1: + __builtin_start_lifetime (&a.b[0]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + case 2: + a.a = 42; + __builtin_start_lifetime (&a.b); + break; + case 3: + a.a = 42; + __builtin_start_lifetime (&a.b[1]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + case 4: + __builtin_start_lifetime (&b.b); + break; + case 5: + __builtin_start_lifetime (&b.b[0]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + case 6: + a.a = 42; + __builtin_start_lifetime (&b.b); + break; + case 7: + a.a = 42; + __builtin_start_lifetime (&b.b[1]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + case 8: + __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + case 9: + __builtin_start_lifetime (&b.b); + __builtin_start_lifetime (&b.b[1]); + __builtin_start_lifetime (&b.b[1].b); + __builtin_start_lifetime (&b.b[1].b[0]); + __builtin_start_lifetime (&b.b[1].b[0][1]); + break; + case 10: + __builtin_start_lifetime (&b.b); + __builtin_start_lifetime (&b.b[1]); + __builtin_start_lifetime (&b.b[1].b); + __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + case 11: + __builtin_start_lifetime (&b.b); + __builtin_start_lifetime (&b.b[1]); + __builtin_start_lifetime (&b.b[1].b[0]); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + case 12: + __builtin_start_lifetime (&b.b); + __builtin_start_lifetime (&b.b[1].b); // { dg-error "'__builtin_start_lifetime' with containing object not within lifetime" } + break; + default: + break; + } + return true; +} +static_assert (fH (0)); +constexpr bool h1 = fH (1); +static_assert (fH (2)); +constexpr bool h3 = fH (3); +static_assert (fH (4)); +constexpr bool h5 = fH (5); +static_assert (fH (6)); +constexpr bool h7 = fH (7); +constexpr bool h8 = fH (8); +static_assert (fH (9)); +constexpr bool h10 = fH (10); +constexpr bool h11 = fH (11); +constexpr bool h12 = fH (12); --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-09-09 17:03:29.407510696 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-09-09 17:08:09.023764764 +0200 @@ -17,12 +17,10 @@ foo (bool x) { std::allocator <int> a; auto b = a.allocate (1); -#if 0 // [allocator.members]/5 says it should start lifetime of the array // but not its elements, and b points to the first element. if (std::is_within_lifetime (b)) return false; -#endif std::construct_at (b); if (!std::is_within_lifetime (b)) return false; --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C 2026-09-09 17:08:09.023919133 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C 2026-09-09 17:08:23.859228524 +0200 @@ -0,0 +1,168 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } + +namespace std { + template <class T> + consteval bool + is_within_lifetime (const T *p) noexcept + { + return __builtin_is_within_lifetime (p); + } + + template <class T> + constexpr void + start_lifetime (T &r) noexcept + { + __builtin_start_lifetime (__builtin_addressof (r)); + } +} + +struct A { int a, b; }; +struct B { A c, d[2]; }; +union C { B e; int f; }; +struct D { C g; B h; }; +struct E { D i[3]; }; +union F { E j; int k; }; + +consteval int +foo () +{ + F l; + l.j.i[0].h.c.a = 1; + if (!std::is_within_lifetime (&l.j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + l.k = 1; + if (std::is_within_lifetime (&l.j.i[1])) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (l.j); + std::start_lifetime (l.j.i); + std::start_lifetime (l.j.i[1]); + if (!std::is_within_lifetime (&l.j.i[1])) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (l.j.i[1].g); + if (!std::is_within_lifetime (&l.j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (l.j.i[1].h); + if (!std::is_within_lifetime (&l.j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (l.j.i[1].h.d); + std::start_lifetime (l.j.i[1].h.d[0]); + if (std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + l.j.i[1].h.d[0].a = 42; + if (!std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (l.j.i[1].g.e); + std::start_lifetime (l.j.i[1].g.e.d); + std::start_lifetime (l.j.i[1].g.e.d[1]); + if (!std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1])) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + l.j.i[1].g.e.d[1].b = 42; + if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1])) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + std::start_lifetime (l.j.i[1].g); + if (!std::is_within_lifetime (&l.j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (l.j.i[1].g); + l.j.i[1].g.f = 42; + if (!std::is_within_lifetime (&l.j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a)) + return __LINE__; + l.k = 1; + return 0; +} + +static_assert (foo () == 0); --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime9.C 2026-09-09 17:08:09.024047950 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime9.C 2026-09-09 17:08:09.024047950 +0200 @@ -0,0 +1,104 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } + +namespace std { + template <class T> + consteval bool + is_within_lifetime (const T *p) noexcept + { + return __builtin_is_within_lifetime (p); + } + + template <class T> + constexpr void + start_lifetime (T &r) noexcept + { + __builtin_start_lifetime (__builtin_addressof (r)); + } +} + +struct A {}; +struct B : A {}; +struct C { B a; }; +union D { A a[3]; B b[3]; C c[3]; }; + +consteval int +foo () +{ + union D d; + if (std::is_within_lifetime (&d.a[0])) + return __LINE__; + if (std::is_within_lifetime (&d.b[1])) + return __LINE__; + if (std::is_within_lifetime (&d.c[2])) + return __LINE__; + if (std::is_within_lifetime (&d.c[2].a)) + return __LINE__; + std::start_lifetime (d.a); + std::start_lifetime (d.a[0]); + if (!std::is_within_lifetime (&d.a[0])) + return __LINE__; + if (std::is_within_lifetime (&d.b[1])) + return __LINE__; + if (std::is_within_lifetime (&d.c[2])) + return __LINE__; + if (std::is_within_lifetime (&d.c[2].a)) + return __LINE__; + std::start_lifetime (d.b); + std::start_lifetime (d.b[1]); + if (std::is_within_lifetime (&d.a[0])) + return __LINE__; + if (!std::is_within_lifetime (&d.b[1])) + return __LINE__; + if (std::is_within_lifetime (&d.c[2])) + return __LINE__; + if (std::is_within_lifetime (&d.c[2].a)) + return __LINE__; + std::start_lifetime (d.c); + std::start_lifetime (d.c[2]); + if (std::is_within_lifetime (&d.a[0])) + return __LINE__; + if (std::is_within_lifetime (&d.b[1])) + return __LINE__; + if (!std::is_within_lifetime (&d.c[2])) + return __LINE__; + if (std::is_within_lifetime (&d.c[2].a)) + return __LINE__; + std::start_lifetime (d.c[2].a); + if (std::is_within_lifetime (&d.a[0])) + return __LINE__; + if (std::is_within_lifetime (&d.b[1])) + return __LINE__; + if (!std::is_within_lifetime (&d.c[2])) + return __LINE__; + if (!std::is_within_lifetime (&d.c[2].a)) + return __LINE__; + if (std::is_within_lifetime (&d.c[1])) + return __LINE__; + if (std::is_within_lifetime (&d.c[1].a)) + return __LINE__; + std::start_lifetime (d.c[1]); + if (!std::is_within_lifetime (&d.c[1])) + return __LINE__; + if (std::is_within_lifetime (&d.c[1].a)) + return __LINE__; + return 0; +} + +static_assert (foo () == 0); + +consteval int +bar () +{ + union D d; + std::start_lifetime (d.c); + std::start_lifetime (d.c[1]); + d.c[1].a = {}; + if (!std::is_within_lifetime (&d.c[1])) + return __LINE__; + if (!std::is_within_lifetime (&d.c[1].a)) + return __LINE__; + return 0; +} + +static_assert (bar () == 0); // { dg-bogus "static assertion failed" "" { xfail *-*-* } } --- a/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C 2026-09-09 17:03:29.407510696 +0200 +++ b/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C 2026-09-09 17:08:09.024204742 +0200 @@ -655,8 +655,8 @@ #ifndef __cpp_trivial_union # error "__cpp_trivial_union" -#elif __cpp_trivial_union != 202502 -# error "__cpp_trivial_union != 202502" +#elif __cpp_trivial_union != 202603 +# error "__cpp_trivial_union != 202603" #endif // C++26 attributes: --- a/libstdc++-v3/include/bits/version.def 2026-09-09 17:03:29.420510528 +0200 +++ b/libstdc++-v3/include/bits/version.def 2026-09-09 17:08:09.024435693 +0200 @@ -2661,6 +2661,15 @@ ftms = { }; }; +ftms = { + name = start_lifetime; + values = { + v = 202603; + cxxmin = 26; + extra_cond = "__has_builtin(__builtin_start_lifetime)"; + }; +}; + // Standard test specifications. stds[97] = ">= 199711L"; stds[03] = ">= 199711L"; --- a/libstdc++-v3/include/bits/version.h 2026-09-09 17:03:29.420510528 +0200 +++ b/libstdc++-v3/include/bits/version.h 2026-09-09 17:08:09.024715555 +0200 @@ -2949,4 +2949,14 @@ #endif /* !defined(__cpp_lib_hardened_view_interface) */ #undef __glibcxx_want_hardened_view_interface +#if !defined(__cpp_lib_start_lifetime) +# if (__cplusplus > 202302L) && (__has_builtin(__builtin_start_lifetime)) +# define __glibcxx_start_lifetime 202603L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_start_lifetime) +# define __cpp_lib_start_lifetime 202603L +# endif +# endif +#endif /* !defined(__cpp_lib_start_lifetime) */ +#undef __glibcxx_want_start_lifetime + #undef __glibcxx_want_all --- a/libstdc++-v3/include/bits/stl_construct.h 2026-09-09 17:03:29.416510580 +0200 +++ b/libstdc++-v3/include/bits/stl_construct.h 2026-09-09 17:08:09.024980314 +0200 @@ -412,6 +412,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } #endif // C++23 +#if __glibcxx_start_lifetime >= 202603L // C++ >= 26 + template <typename _Tp> + constexpr void + start_lifetime(_Tp& __r) noexcept + { +#ifdef __cpp_lib_is_implicit_lifetime + static_assert(std::is_implicit_lifetime_v<_Tp>); +#endif +#ifdef __cpp_lib_is_aggregate + static_assert(std::is_aggregate_v<_Tp>); +#endif + __builtin_start_lifetime(__builtin_addressof(__r)); + } +#endif // C++26 + _GLIBCXX_END_NAMESPACE_VERSION } // namespace std --- a/libstdc++-v3/include/std/memory 2026-09-09 17:03:29.420510528 +0200 +++ b/libstdc++-v3/include/std/memory 2026-09-09 17:08:09.025156732 +0200 @@ -127,6 +127,7 @@ #define __glibcxx_want_transparent_operators #define __glibcxx_want_smart_ptr_owner_equality #define __glibcxx_want_allocate_at_least +#define __glibcxx_want_start_lifetime #include <bits/version.h> #if __cplusplus >= 201103L && __cplusplus <= 202002L && _GLIBCXX_HOSTED --- a/libstdc++-v3/src/c++23/std.cc.in 2026-09-09 17:03:29.420510528 +0200 +++ b/libstdc++-v3/src/c++23/std.cc.in 2026-09-09 17:08:09.025416416 +0200 @@ -2074,6 +2074,9 @@ export namespace std #ifdef __glibcxx_allocate_at_least using std::allocation_result; #endif +#if __cpp_lib_start_lifetime + using std::start_lifetime; +#endif } // 20.4 <memory_resource> Jakub
