Hi! Here is an updated version of the P3726R2 paper implementation, originally posted in May together with the other trivial union paper and one CWG: https://gcc.gnu.org/pipermail/gcc-patches/2026-May/thread.html#717349 That other paper and CWG has been committed in the meantime, this one was deferred so that more accurate lifetime tracking is implemented (which was partially done in the recently committed std::is_within_lifetime patch).
The major change from the earlier patch is that a new CONSTRUCTOR flag CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P is added, which represents both the C++20 std::allocator<T>::allocate required behavior as well as P3726R2 std::start_lifetime, where the CONSTRUCTOR itself is considered to be within lifetime, but none of its subobjects. This flag is then tested in reduced_constant_expression_p to determine which union elemental subobjects are actually inactive and only those are allowed to be omitted, and in the recently added __builtin_is_within_lifetime builtin constant evaluation. So far lightly tested, with: GXX_TESTSUITE_STDS=98,11,14,17,20,23,26,29 make check-g++ RUNTESTFLAGS="dg.exp='within-lifetime* trivial-union* feat-cxx2* constexpr-life*'" ok for trunk if it passes full bootstrap/regtest? 2026-09-04 Jakub Jelinek <[email protected]> P3726R2 PR c++/125845 gcc/c-family/ * c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine __cpp_trivial_union to 202603L. gcc/cp/ * cp-tree.h: Implement C++26 P3726R2 - Adjustments to Union Lifetime Rules. (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P): Define. (enum cp_built_in_function): Add CP_BUILT_IN_START_LIFETIME. (reduced_constant_expression_p): Add a bool argument defaulted to false. * tree.cc (builtin_valid_in_constant_expr_p): Handle CP_BUILT_IN_START_LIFETIME. * decl.cc (cxx_init_decl_processing): Create __builtin_start_lifetime decl. * cp-gimplify.cc (cp_gimplify_expr): Handle CP_BUILT_IN_START_LIFETIME. * semantics.cc (finish_call_expr): Likewise. * constexpr.cc (cxx_eval_is_within_lifetime): For subobjects of CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P return boolean_false_node. (cxx_eval_start_lifetime): New function. (cxx_eval_builtin_function_call): Handle CP_BUILT_IN_START_LIFETIME. (reduced_constant_expression_p): Add union_elemental_subobject argument and propagate it to recursive calls. If set, ignore holes in array initializers which aren't within lifetime. (cxx_eval_store_expression): Add start_lifetime argument. Don't preevaluate init in tha tcase, return void_node early if *valp is already non-NULL/void_node and within lifetime. (cxx_eval_constant_expression): For std::allocator<T>::allocate start lifetime of the created array but not of its subobjects. gcc/testsuite/ * g++.dg/cpp26/feat-cxx26.C: Adjust __cpp_trivial_union checking. * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking. * g++.dg/cpp26/trivial-union3.C: New test. * g++.dg/cpp26/trivial-union4.C: New test. * g++.dg/cpp26/trivial-union5.C: New test. * g++.dg/cpp26/trivial-union6.C: New test. * g++.dg/cpp26/within-lifetime3.C: Remove #if 0 and #endif. * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking. libstdc++-v3/ * include/bits/version.def (start_lifetime): New. * include/bits/version.h: Regenerate. * include/bits/stl_construct.h (std::start_lifetime): New function template. * include/std/memory: Define __glibcxx_want_start_lifetime before including bits/version.h. * src/c++23/std.cc.in: Add export std::start_lifetime. --- a/gcc/c-family/c-cppbuiltin.cc 2026-09-03 09:43:50.763477848 +0200 +++ b/gcc/c-family/c-cppbuiltin.cc 2026-09-04 13:54:14.074058589 +0200 @@ -1124,7 +1124,7 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_impl_reflection=202603L"); else cpp_warn (pfile, "__cpp_impl_reflection"); - cpp_define (pfile, "__cpp_trivial_union=202502L"); + cpp_define (pfile, "__cpp_trivial_union=202603L"); } if (cxx_dialect > cxx26) { --- a/gcc/cp/cp-tree.h 2026-09-03 09:45:43.310945741 +0200 +++ b/gcc/cp/cp-tree.h 2026-09-04 14:11:50.412816957 +0200 @@ -5098,6 +5098,10 @@ get_vec_init_expr (tree t) #define CONSTRUCTOR_BRACES_ELIDED_P(NODE) \ (CONSTRUCTOR_CHECK (NODE)->base.protected_flag) +/* True if omitted fields are considered to be not within lifetime. */ +#define CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P(NODE) \ + (CONSTRUCTOR_CHECK (NODE)->base.deprecated_flag) + /* True if NODE represents a conversion for direct-initialization in a template. Set by perform_implicit_conversion_flags. */ #define IMPLICIT_CONV_EXPR_DIRECT_INIT(NODE) \ @@ -7212,6 +7216,7 @@ enum cp_built_in_function { CP_BUILT_IN_CURRENT_EXCEPTION, CP_BUILT_IN_UNCAUGHT_EXCEPTIONS, CP_BUILT_IN_IS_WITHIN_LIFETIME, + CP_BUILT_IN_START_LIFETIME, CP_BUILT_IN_LAST }; @@ -9413,7 +9418,7 @@ extern tree fold_non_dependent_init (tr bool = false, tree = NULL_TREE); extern tree fold_simple (tree); extern tree fold_to_constant (tree); -extern bool reduced_constant_expression_p (tree, tree = NULL_TREE); +extern bool reduced_constant_expression_p (tree, tree = NULL_TREE, bool = false); extern bool is_instantiation_of_constexpr (tree); extern bool var_in_constexpr_fn (tree); extern bool var_in_maybe_constexpr_fn (tree); --- a/gcc/cp/tree.cc 2026-09-03 09:43:50.789477496 +0200 +++ b/gcc/cp/tree.cc 2026-09-04 13:54:14.075685154 +0200 @@ -579,6 +579,7 @@ builtin_valid_in_constant_expr_p (const_ case CP_BUILT_IN_CURRENT_EXCEPTION: case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS: case CP_BUILT_IN_IS_WITHIN_LIFETIME: + case CP_BUILT_IN_START_LIFETIME: return true; default: break; --- a/gcc/cp/decl.cc 2026-09-03 09:45:43.308945768 +0200 +++ b/gcc/cp/decl.cc 2026-09-04 13:54:14.077036599 +0200 @@ -5679,6 +5679,13 @@ cxx_init_decl_processing (void) set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); SET_DECL_IMMEDIATE_FUNCTION_P (decl); + tree void_vaftype = build_varargs_function_type_list (boolean_type_node, + NULL_TREE); + decl = add_builtin_function ("__builtin_start_lifetime", + void_vaftype, CP_BUILT_IN_START_LIFETIME, + BUILT_IN_FRONTEND, NULL, NULL_TREE); + set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); + integer_two_node = build_int_cst (NULL_TREE, 2); /* Guess at the initial static decls size. */ --- a/gcc/cp/cp-gimplify.cc 2026-09-03 09:43:50.774477699 +0200 +++ b/gcc/cp/cp-gimplify.cc 2026-09-04 13:54:14.078081642 +0200 @@ -1036,6 +1036,7 @@ cp_gimplify_expr (tree *expr_p, gimple_s 0)); break; case CP_BUILT_IN_CONSTEXPR_DIAG: + case CP_BUILT_IN_START_LIFETIME: *expr_p = void_node; break; default: --- a/gcc/cp/semantics.cc 2026-09-03 09:45:43.312945713 +0200 +++ b/gcc/cp/semantics.cc 2026-09-04 14:10:43.423660866 +0200 @@ -3541,30 +3541,74 @@ finish_call_expr (tree fn, vec<tree, va_ } if (TREE_CODE (fn) == FUNCTION_DECL - && fndecl_built_in_p (fn, CP_BUILT_IN_IS_WITHIN_LIFETIME, - BUILT_IN_FRONTEND)) - { - /* Unless users call the builtin directly, the following 2 checks - should be ensured from std::is_within_lifetime template. */ - if (vec_safe_length (*args) != 1) - { - if (complain & tf_error) - error ("%<__builtin_is_within_lifetime%> needs a single " - "argument"); + && fndecl_built_in_p (fn, BUILT_IN_FRONTEND)) + switch (DECL_UNCHECKED_FUNCTION_CODE (fn)) + { + tree arg, ptype, type; + case CP_BUILT_IN_IS_WITHIN_LIFETIME: + /* Unless users call the builtin directly, the following 2 checks + should be ensured from std::is_within_lifetime template. */ + if (vec_safe_length (*args) != 1) + { + if (complain & tf_error) + error ("%qs needs a single argument", + "__builtin_is_within_lifetime"); + return error_mark_node; + } + arg = (**args)[0]; + if (error_operand_p (arg)) return error_mark_node; - } - tree arg = (**args)[0]; - if (error_operand_p (arg)) - return error_mark_node; - tree ptype = TREE_TYPE (arg); - if (!POINTER_TYPE_P (ptype)) - { - if (complain & tf_error) - error ("%<__builtin_is_within_lifetime%> argument type " - "%qT is not pointer type", ptype); + ptype = TREE_TYPE (arg); + if (!POINTER_TYPE_P (ptype)) + { + if (complain & tf_error) + error ("%qs argument type %qT is not pointer type", + "__builtin_is_within_lifetime", ptype); + return error_mark_node; + } + break; + case CP_BUILT_IN_START_LIFETIME: + /* Unless users call the builtin directly, the following 2 checks + should be ensured from std::start_lifetime template. */ + if (vec_safe_length (*args) != 1) + { + if (complain & tf_error) + error ("%qs needs a single argument", + "__builtin_start_lifetime"); + return error_mark_node; + } + arg = (**args)[0]; + if (error_operand_p (arg)) return error_mark_node; - } - } + ptype = TREE_TYPE (arg); + if (!POINTER_TYPE_P (ptype)) + { + if (complain & tf_error) + error ("%qs argument type %qT is not pointer type", + "__builtin_start_lifetime", ptype); + return error_mark_node; + } + type = TREE_TYPE (ptype); + if (!complete_type_or_else (type, NULL_TREE)) + return error_mark_node; + if (!CP_AGGREGATE_TYPE_P (type)) + { + if (complain & tf_error) + error ("%qs argument type %qT is not a pointer to " + "aggregate type", "__builtin_start_lifetime", + ptype); + return error_mark_node; + } + if (!implicit_lifetime_type_p (type)) + { + if (complain & tf_error) + error ("%qs argument type %qT is not a pointer to " + "implicit-lifetime type", + "__builtin_start_lifetime", ptype); + return error_mark_node; + } + break; + } /* A call to a namespace-scope function. */ result = build_new_function_call (fn, args, orig_complain); --- a/gcc/cp/constexpr.cc 2026-09-03 09:43:50.766477808 +0200 +++ b/gcc/cp/constexpr.cc 2026-09-04 14:32:59.700779280 +0200 @@ -2748,7 +2748,11 @@ cxx_eval_is_within_lifetime (const const if (ref == NULL_TREE) continue; if (CONSTRUCTOR_NO_CLEARING (val)) - val = val_uninit; + { + if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val)) + return boolean_false_node; + val = val_uninit; + } else val = val_zero_init; continue; @@ -2775,7 +2779,11 @@ cxx_eval_is_within_lifetime (const const continue; } if (CONSTRUCTOR_NO_CLEARING (val)) - val = val_uninit; + { + if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val)) + return boolean_false_node; + val = val_uninit; + } else val = val_zero_init; continue; @@ -2785,6 +2793,41 @@ cxx_eval_is_within_lifetime (const const return boolean_true_node; } +static tree cxx_eval_store_expression (const constexpr_ctx *, tree, + value_cat, bool *, bool *, + tree *, bool = false); + +/* Attempt to evaluate T which represents a call to + __builtin_start_lifetime. */ + +static tree +cxx_eval_start_lifetime (const constexpr_ctx *ctx, tree t, bool *non_constant_p, + bool *overflow_p, tree *jump_target) +{ + location_t loc = EXPR_LOCATION (t); + tree arg = CALL_EXPR_ARG (t, 0); + arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg); + arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + tree ctor = build_constructor (TREE_TYPE (arg), NULL); + CONSTRUCTOR_NO_CLEARING (ctor) = 1; + CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1; + tree modexpr = build2_loc (loc, MODIFY_EXPR, TREE_TYPE (arg), arg, ctor); + arg = cxx_eval_store_expression (ctx, modexpr, vc_discard, non_constant_p, + overflow_p, jump_target, true); + ggc_free (modexpr); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + return void_node; +} + /* Attempt to evaluate T which represents a call to a builtin function. We assume here that all builtin functions evaluate to scalar types represented by _CST nodes. */ @@ -2862,6 +2905,10 @@ cxx_eval_builtin_function_call (const co return cxx_eval_is_within_lifetime (ctx, t, non_constant_p, overflow_p, jump_target); + case CP_BUILT_IN_START_LIFETIME: + return cxx_eval_start_lifetime (ctx, t, non_constant_p, overflow_p, + jump_target); + default: break; } @@ -5107,10 +5154,13 @@ cxx_eval_call_expression (const constexp initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will be cleared. If called recursively on a FIELD_DECL's CONSTRUCTOR, SZ is DECL_SIZE of the FIELD_DECL, otherwise NULL. + UNION_ELEMENTAL_SUBOBJECT is true when recursing on C++ >= 26 union + elemental subobjects. FIXME speed this up, it's taking 16% of compile time on sieve testcase. */ bool -reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */) +reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */, + bool union_elemental_subobject /* = false */) { if (t == NULL_TREE) return false; @@ -5134,7 +5184,11 @@ reduced_constant_expression_p (tree t, t return false; if (CONSTRUCTOR_NO_CLEARING (t)) { - if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) + if (union_elemental_subobject + && TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE + && CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (t)) + field = NULL_TREE; + else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) { /* There must be a valid constant initializer at every array index. */ @@ -5168,12 +5222,25 @@ reduced_constant_expression_p (tree t, t active member has no constituent values, so all constituent values are constant. */ field = NULL_TREE; + union_elemental_subobject = cxx_dialect >= cxx26; } else - field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t))); + { + field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t))); + union_elemental_subobject = false; + } } else - field = NULL_TREE; + { + field = NULL_TREE; + if (cxx_dialect >= cxx26) + { + if (TREE_CODE (TREE_TYPE (t)) == UNION_TYPE) + union_elemental_subobject = true; + else if (TREE_CODE (TREE_TYPE (t)) != ARRAY_TYPE) + union_elemental_subobject = false; + } + } for (auto &e: CONSTRUCTOR_ELTS (t)) { /* If VAL is null, we're in the middle of initializing this @@ -5183,7 +5250,8 @@ reduced_constant_expression_p (tree t, t && (TREE_CODE (e.index) == FIELD_DECL)) ? DECL_SIZE (e.index) - : NULL_TREE)) + : NULL_TREE, + union_elemental_subobject)) return false; /* We want to remove initializers for empty fields in a struct to avoid confusing output_constructor. */ @@ -8213,13 +8281,14 @@ modifying_const_object_p (tree_code code return false; } -/* Evaluate an INIT_EXPR or MODIFY_EXPR. */ +/* Evaluate an INIT_EXPR or MODIFY_EXPR. START_LIFETIME is true + when evaluating __builtin_start_lifetime. */ static tree cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, value_cat lval, bool *non_constant_p, bool *overflow_p, - tree *jump_target) + tree *jump_target, bool start_lifetime/*=false*/) { constexpr_ctx new_ctx = *ctx; @@ -8232,7 +8301,7 @@ cxx_eval_store_expression (const constex tree type = TREE_TYPE (target); bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR; - if (preeval && !TREE_CLOBBER_P (init)) + if (preeval && !TREE_CLOBBER_P (init) && !start_lifetime) { /* Ignore var = .DEFERRED_INIT (); for now, until PR121965 is fixed. */ if (flag_auto_var_init > AUTO_INIT_UNINITIALIZED @@ -8573,12 +8642,13 @@ cxx_eval_store_expression (const constex index); *non_constant_p = true; } - else if (!is_access_expr - || (TREE_CLOBBER_P (init) - && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) - || (TREE_CODE (t) == MODIFY_EXPR - && CLASS_TYPE_P (inner) - && !type_has_non_deleted_trivial_default_ctor (inner))) + else if ((!is_access_expr + || (TREE_CLOBBER_P (init) + && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) + || (TREE_CODE (t) == MODIFY_EXPR + && CLASS_TYPE_P (inner) + && !type_has_non_deleted_trivial_default_ctor (inner))) + && !start_lifetime) { /* Diagnose changing active union member after initialization without a valid member access expression, as described in @@ -8675,6 +8745,11 @@ cxx_eval_store_expression (const constex empty_base = true; } + /* For __builtin_start_lifetime, if it is already within lifetime, + don't do anything. */ + if (start_lifetime && *valp != NULL_TREE && *valp != void_node) + return void_node; + /* Detect modifying a constant object in constexpr evaluation. We have found a const object that is being modified. Figure out if we need to issue an error. Consider @@ -8780,7 +8855,7 @@ cxx_eval_store_expression (const constex || *valp == void_node || (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (*valp), type))); - if (empty_base) + if (empty_base && !start_lifetime) { /* Just evaluate the initializer and return, since there's no actual data to store, and we didn't build a CONSTRUCTOR. */ @@ -10730,6 +10805,17 @@ cxx_eval_constant_expression (const cons TREE_TYPE (var) = new_type; TREE_TYPE (TREE_OPERAND (op, 0)) = build_pointer_type (TREE_TYPE (var)); + if (is_std_allocator_allocate (ctx->call) + && id_equal (DECL_NAME (ctx->call->fundef->decl), "allocate")) + { + /* [allocator.members]/5: This function starts the lifetime + of the array object, but not that of any of the array + elements. */ + tree ctor = build_constructor (new_type, NULL); + CONSTRUCTOR_NO_CLEARING (ctor) = 1; + CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1; + ctx->global->put_value (var, ctor); + } } /* This can happen for std::meta::info(^^int) where the cast has no --- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-09-03 09:43:50.797477387 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-09-04 13:54:14.080797712 +0200 @@ -655,8 +655,8 @@ #ifndef __cpp_trivial_union # error "__cpp_trivial_union" -#elif __cpp_trivial_union != 202502 -# error "__cpp_trivial_union != 202502" +#elif __cpp_trivial_union != 202603 +# error "__cpp_trivial_union != 202603" #endif // C++26 attributes: --- a/gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-09-04 13:54:14.080919178 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-09-04 13:54:14.080919178 +0200 @@ -0,0 +1,28 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do compile { target c++26 } } + +#include <memory> +#include <string> + +template <typename T, size_t N> +struct FixedVector { + union { T storage[N]; }; + size_t size = 0; + + constexpr FixedVector () { std::start_lifetime (storage); } + + constexpr ~FixedVector() { std::destroy(storage, storage + size); } + + constexpr void push_back(T const& v) { ::new (storage + size) T(v); ++size; } +}; + +constexpr size_t +silly_test () +{ + FixedVector <std::string, 3> v; + v.push_back ("some sufficiently longer string"); + return v.size; +} + +static_assert (silly_test () == 1); --- a/gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-09-04 13:54:14.081027425 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-09-04 13:54:14.081027425 +0200 @@ -0,0 +1,63 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do compile { target c++26 } } + +namespace std { + template <class T> + constexpr void + start_lifetime (T &r) noexcept + { + __builtin_start_lifetime (__builtin_addressof (r)); + } +} + +struct S { int a; }; +union U { int a; S b[3]; }; +union V { int a; U b[2]; }; + +consteval +{ + S s; + s.a = 42; + std::start_lifetime (s); + if (s.a != 42) + throw 1; + U u; + u.b[0].a = 42; + std::start_lifetime (u.b); + std::start_lifetime (u.b[0]); + std::start_lifetime (u.b[2]); + u.a = 42; + std::start_lifetime (u.b); + std::start_lifetime (u.b[0]); + std::start_lifetime (u.b[1]); + V v; + v.a = 42; + std::start_lifetime (v.b); + std::start_lifetime (v.b[1]); + v.b[1].a = 42; + std::start_lifetime (v.b[1].b); + std::start_lifetime (v.b[1].b[1]); + v.b[1].b[1].a = 43; + std::start_lifetime (v.b); + std::start_lifetime (v.b[1].b[1]); + if (v.b[1].b[1].a != 43) + throw 2; + const S cs = { 42 }; + std::start_lifetime (cs); + if (cs.a != 42) + throw 3; + const U cu = { .b = { { 41 }, { 42 }, { 43 } } }; + std::start_lifetime (cu.b); + std::start_lifetime (cu.b[0]); + std::start_lifetime (cu.b[2]); + if (cu.b[2].a != 43) + throw 4; + const V cv = { .b = { { .b = { { 44 } } } } }; + std::start_lifetime (cv.b); + std::start_lifetime (cv.b[0]); + std::start_lifetime (cv.b[0].b); + std::start_lifetime (cv.b[0].b[0]); + if (cv.b[0].b[0].a != 44) + throw 5; +} --- a/gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-09-04 13:54:14.081120178 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-09-04 13:54:14.081120178 +0200 @@ -0,0 +1,37 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do compile { target c++26 } } + +struct S { int a; }; +S a = { 5 }; +struct T { constexpr T () : a (42) {} int a; }; +struct R { int a; constexpr ~R () {} }; +union U { int a; S b[2]; }; + +consteval { int a = 5; __builtin_start_lifetime (&a); } // { dg-error "'__builtin_start_lifetime' argument type 'int\\\*' is not a pointer to aggregate type" } +consteval { __builtin_start_lifetime (&a); }; // { dg-error "modification of 'a' from outside current evaluation is not a constant expression" } +consteval { __builtin_start_lifetime (); }; // { dg-error "'__builtin_start_lifetime' needs a single argument" } +consteval { S a = { 6 }; __builtin_start_lifetime (&a, &a); }; // { dg-error "'__builtin_start_lifetime' needs a single argument" } +consteval { __builtin_start_lifetime (42); }; // { dg-error "'__builtin_start_lifetime' argument type 'int' is not pointer type" } +consteval { T t; __builtin_start_lifetime (&t); } // { dg-error "'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer to aggregate type" } +consteval { R r; __builtin_start_lifetime (&r); } // { dg-error "'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer to implicit-lifetime type" } +consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.a); } // { dg-error "'__builtin_start_lifetime' argument type 'const int\\\*' is not a pointer to aggregate type" } +consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.b); } // { dg-error "modifying a const object 'u.U::b' is not allowed in a constant expression" } + +void +foo () +{ + S s; + __builtin_start_lifetime (&s); + int a = 5; + __builtin_start_lifetime (&a); // { dg-error "'__builtin_start_lifetime' argument type 'int\\\*' is not a pointer to aggregate type" } + __builtin_start_lifetime (&::a); + __builtin_start_lifetime (); // { dg-error "'__builtin_start_lifetime' needs a single argument" } + S b = { 6 }; + __builtin_start_lifetime (&b, &b); // { dg-error "'__builtin_start_lifetime' needs a single argument" } + __builtin_start_lifetime (42); // { dg-error "'__builtin_start_lifetime' argument type 'int' is not pointer type" } + T t; + __builtin_start_lifetime (&t); // { dg-error "'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer to aggregate type" } + R r; + __builtin_start_lifetime (&r); // { dg-error "'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer to implicit-lifetime type" } +} --- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-04 13:54:14.081215755 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-04 13:54:14.081215755 +0200 @@ -0,0 +1,70 @@ +// P3074R7 - trivial unions (was std::uninitialized<T>) +// P3726R2 - Adjustments to Union Lifetime Rules +// { dg-do compile { target c++20 } } + +struct A { int a[2]; }; +union B { int a; int b[2]; }; +union C { int a; int b[2][2]; }; +struct D { B a; }; +struct E { C a; }; +union F { int a; A b; }; +union G { int a; A b[2]; }; + +constexpr A fA () { A a; a.a[0] = 1; return a; } +constexpr A a = fA (); // { dg-error "is not a constant expression" "" { xfail *-*-* } } +constexpr B fB () { B a; a.b[0] = 1; return a; } +constexpr B b = fB (); // { dg-error "is not a constant expression" } +constexpr C fC () { C a; a.b[0][0] = 1; a.b[1][0] = 2; a.b[1][1] = 3; return a; } +constexpr C c = fC (); // { dg-error "is not a constant expression" } +constexpr D fD () { D a; a.a.b[0] = 1; return a; } +constexpr D d = fD (); // { dg-error "is not a constant expression" } +constexpr E fE () { E a; a.a.b[0][0] = 1; a.a.b[1][0] = 2; a.a.b[1][1] = 3; return a; } +constexpr E e = fE (); // { dg-error "is not a constant expression" } +constexpr F fF () { F a; a.b.a[0] = 1; return a; } +constexpr F f = fF (); // { dg-error "is not a constant expression" } +constexpr G fG () { G a; a.b[0].a[0] = 1; return a; } +constexpr G g = fG (); // { dg-error "is not a constant expression" } +constexpr B +fB2 () +{ + B a; + __builtin_start_lifetime (&a.b); + a.b[0] = 1; + return a; +} +constexpr B b2 = fB2 (); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr C +fC2 () +{ + C a; + __builtin_start_lifetime (&a.b); + __builtin_start_lifetime (&a.b[0]); + __builtin_start_lifetime (&a.b[1]); + a.b[0][0] = 1; + a.b[1][0] = 2; + a.b[1][1] = 3; + return a; +} +constexpr C c2 = fC2 (); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr D +fD2 () +{ + D a; + __builtin_start_lifetime (&a.a.b); + a.a.b[0] = 1; + return a; +} +constexpr D d2 = fD2 (); // { dg-error "is not a constant expression" "" { target c++23_down } } +constexpr E +fE2 () +{ + E a; + __builtin_start_lifetime (&a.a.b); + __builtin_start_lifetime (&a.a.b[0]); + __builtin_start_lifetime (&a.a.b[1]); + a.a.b[0][0] = 1; + a.a.b[1][0] = 2; + a.a.b[1][1] = 3; + return a; +} +constexpr E e2 = fE2 (); // { dg-error "is not a constant expression" "" { target c++23_down } } --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-09-03 09:43:50.797477387 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-09-04 13:54:14.081341081 +0200 @@ -17,12 +17,10 @@ foo (bool x) { std::allocator <int> a; auto b = a.allocate (1); -#if 0 // [allocator.members]/5 says it should start lifetime of the array // but not its elements, and b points to the first element. if (std::is_within_lifetime (b)) return false; -#endif std::construct_at (b); if (!std::is_within_lifetime (b)) return false; --- a/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C 2026-09-03 09:43:50.797477387 +0200 +++ b/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C 2026-09-04 13:54:14.081504434 +0200 @@ -655,8 +655,8 @@ #ifndef __cpp_trivial_union # error "__cpp_trivial_union" -#elif __cpp_trivial_union != 202502 -# error "__cpp_trivial_union != 202502" +#elif __cpp_trivial_union != 202603 +# error "__cpp_trivial_union != 202603" #endif // C++26 attributes: --- a/libstdc++-v3/include/bits/version.def 2026-09-03 09:43:50.805477279 +0200 +++ b/libstdc++-v3/include/bits/version.def 2026-09-04 13:54:14.114036138 +0200 @@ -2661,6 +2661,15 @@ ftms = { }; }; +ftms = { + name = start_lifetime; + values = { + v = 202603; + cxxmin = 26; + extra_cond = "__has_builtin(__builtin_start_lifetime)"; + }; +}; + // Standard test specifications. stds[97] = ">= 199711L"; stds[03] = ">= 199711L"; --- a/libstdc++-v3/include/bits/version.h 2026-09-03 09:43:50.805477279 +0200 +++ b/libstdc++-v3/include/bits/version.h 2026-09-04 13:54:14.115036126 +0200 @@ -2949,4 +2949,14 @@ #endif /* !defined(__cpp_lib_hardened_view_interface) */ #undef __glibcxx_want_hardened_view_interface +#if !defined(__cpp_lib_start_lifetime) +# if (__cplusplus > 202302L) && (__has_builtin(__builtin_start_lifetime)) +# define __glibcxx_start_lifetime 202603L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_start_lifetime) +# define __cpp_lib_start_lifetime 202603L +# endif +# endif +#endif /* !defined(__cpp_lib_start_lifetime) */ +#undef __glibcxx_want_start_lifetime + #undef __glibcxx_want_all --- a/libstdc++-v3/include/bits/stl_construct.h 2026-09-03 09:43:50.803477306 +0200 +++ b/libstdc++-v3/include/bits/stl_construct.h 2026-09-04 13:54:14.116046285 +0200 @@ -412,6 +412,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } #endif // C++23 +#if __glibcxx_start_lifetime >= 202603L // C++ >= 26 + template <typename _Tp> + constexpr void + start_lifetime(_Tp& __r) noexcept + { +#ifdef __cpp_lib_is_implicit_lifetime + static_assert(std::is_implicit_lifetime_v<_Tp>); +#endif +#ifdef __cpp_lib_is_aggregate + static_assert(std::is_aggregate_v<_Tp>); +#endif + __builtin_start_lifetime(__builtin_addressof(__r)); + } +#endif // C++26 + _GLIBCXX_END_NAMESPACE_VERSION } // namespace std --- a/libstdc++-v3/include/std/memory 2026-09-03 09:43:50.811477198 +0200 +++ b/libstdc++-v3/include/std/memory 2026-09-04 13:54:14.116326618 +0200 @@ -127,6 +127,7 @@ #define __glibcxx_want_transparent_operators #define __glibcxx_want_smart_ptr_owner_equality #define __glibcxx_want_allocate_at_least +#define __glibcxx_want_start_lifetime #include <bits/version.h> #if __cplusplus >= 201103L && __cplusplus <= 202002L && _GLIBCXX_HOSTED --- a/libstdc++-v3/src/c++23/std.cc.in 2026-09-03 09:43:50.811477198 +0200 +++ b/libstdc++-v3/src/c++23/std.cc.in 2026-09-04 13:54:14.116872978 +0200 @@ -2074,6 +2074,9 @@ export namespace std #ifdef __glibcxx_allocate_at_least using std::allocation_result; #endif +#if __cpp_lib_start_lifetime + using std::start_lifetime; +#endif } // 20.4 <memory_resource> Jakub
