On Mon, Sep 7, 2026 at 7:55 AM Tomasz Kaminski <[email protected]> wrote:
> > > On Fri, Sep 4, 2026 at 3:57 PM Jakub Jelinek <[email protected]> wrote: > >> 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; } >> > I would repurpose this test, and do: E a; start_lifetime(&a.a.b[0]); a.a.b[0][0] = 1; // a.a.b[0][1] is not withing lifetime so OK a.a.b[1][1] = 3; return a; // This should still be ill-formed due a.a.b[1][0] being erronous. > +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]); >> > Could you add test where you remove this start lifetime? > >> + a.b[0][0] = 1; >> + a.b[1][0] = 2; >> + a.b[1][1] = 3; >> > Then the first of above assignments will create an a.b[1] array object, > and second > will make it fully uninitialized. The other combiation would be to keep > start_lifetime on > &a.b[1]. but remove one of the assignments. To avoid repetition you could > check one combination > in fC2 and other in fE2, as they both now test same scenario. > >> + 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 >> >>
