Hi! Is my understanding correct that for stores changing the active union member at constant evaluation time we want to start lifetime of all subobjects except variant members, while std::start_lifetime only does that without any subobjects?
Furthermore, there are the https://wg21.link/P2641R4 and https://wg21.link/P3450R1 std::is_within_lifetime papers which we need to implement and track lifetime even in more detail. Looking at clang, they do implement the first paper using __builtin_is_within_lifetime builtin, but not the second one; wonder if the second paper is implementable purely in headers (somehow try to evaluate if static_cast<const volatile U*>(p) is a constant expression) or whether we need a two argument builtin instead of one (where the other argument would be say a null pointer of the const volatile U * type) and how to deal with compatibility when LLVM 22 already has __has_builtin(__builtin_is_within_lifetime) but not support the 2 argument one. Anyway, my limited understanding is that lifetime of an object starts at the end of a constructor or during vacuous initialization and ends when destructor starts, the object is destroyed or reused. So, I think solely for __builtin_start_lifetime purposes we need to differentiate between CONSTRUCTOR_NO_CLEARING CONSTRUCTORs which do have all omitted elements within lifetime and such constructors which have all omitted elements not within lifetime, but then also need a flag on each CONSTRUCTOR whether that whole subobject is within lifetime. Does e.g. ptr[2].~T (); on an array previously all within lifetime make that particular element not within a lifetime? Or only for class types? The hole checking in the patch then would need to differentiate between the within lifetime and not within lifetime holes. Attaching just for tracking purposes the extracted uncommitted part of the trivial union patchset (rest has been committed in r17-671), so far unmodified further. 2026-05-21 Jakub Jelinek <[email protected]> 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. (enum cp_built_in_function): Add CP_BUILT_IN_START_LIFETIME. (check_builtin_start_lifetime): Declare. (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_builtin_start_lifetime): New function. * constexpr.cc (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. (cxx_eval_store_expression): Add start_lifetime argument. Don't preevaluate init in tha tcase, return early if !seen_union after loop around the possibly nested ARRAY_REFs/COMPONENT_REFs etc. or when all union members are already active. 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. 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. --- gcc/c-family/c-cppbuiltin.cc.jj 2026-05-20 08:43:02.545478650 +0200 +++ gcc/c-family/c-cppbuiltin.cc 2026-05-20 14:26:01.233439033 +0200 @@ -1122,7 +1122,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 (flag_concepts && cxx_dialect > cxx14) cpp_define (pfile, "__cpp_concepts=202002L"); --- gcc/cp/cp-tree.h.jj 2026-05-20 08:43:07.245399375 +0200 +++ gcc/cp/cp-tree.h 2026-05-20 14:26:01.237438967 +0200 @@ -7245,6 +7245,7 @@ enum cp_built_in_function { CP_BUILT_IN_CONSTEXPR_DIAG, CP_BUILT_IN_CURRENT_EXCEPTION, CP_BUILT_IN_UNCAUGHT_EXCEPTIONS, + CP_BUILT_IN_START_LIFETIME, CP_BUILT_IN_LAST }; @@ -8666,6 +8667,7 @@ extern tree fold_builtin_is_correspondin extern bool pointer_interconvertible_base_of_p (tree, tree, bool = false); extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *); extern tree fold_builtin_is_string_literal (location_t, int, tree *); +extern bool check_builtin_start_lifetime (location_t, int, tree *, tsubst_flags_t); extern tree finish_structured_binding_size (location_t, tree, tsubst_flags_t); extern tree finish_trait_expr (location_t, enum cp_trait_kind, tree, tree); extern tree finish_trait_type (enum cp_trait_kind, tree, tree, tsubst_flags_t); @@ -9435,7 +9437,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); --- gcc/cp/tree.cc.jj 2026-05-20 08:43:02.572478195 +0200 +++ gcc/cp/tree.cc 2026-05-20 14:26:01.238177470 +0200 @@ -574,6 +574,7 @@ builtin_valid_in_constant_expr_p (const_ case CP_BUILT_IN_CONSTEXPR_DIAG: case CP_BUILT_IN_CURRENT_EXCEPTION: case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS: + case CP_BUILT_IN_START_LIFETIME: return true; default: break; --- gcc/cp/decl.cc.jj 2026-05-20 08:43:02.564478330 +0200 +++ gcc/cp/decl.cc 2026-05-20 14:26:01.240438917 +0200 @@ -5663,6 +5663,13 @@ cxx_init_decl_processing (void) BUILT_IN_FRONTEND, NULL, NULL_TREE); set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); + 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. */ --- gcc/cp/cp-gimplify.cc.jj 2026-05-20 08:43:02.554478498 +0200 +++ gcc/cp/cp-gimplify.cc 2026-05-20 14:26:01.241438900 +0200 @@ -1015,6 +1015,14 @@ 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: + check_builtin_start_lifetime (EXPR_LOCATION (*expr_p), + call_expr_nargs (*expr_p), + call_expr_nargs (*expr_p) + ? &CALL_EXPR_ARG (*expr_p, 0) + : NULL, + tf_warning_or_error); + *expr_p = void_node; default: break; } --- gcc/cp/semantics.cc.jj 2026-05-20 08:43:07.247399342 +0200 +++ gcc/cp/semantics.cc 2026-05-20 14:26:01.242438884 +0200 @@ -13989,6 +13989,53 @@ fold_builtin_is_string_literal (location return boolean_true_node; } +/* Perform __builtin_start_lifetime call checking, return false + when errors are reported. */ + +bool +check_builtin_start_lifetime (location_t loc, int nargs, tree *args, + tsubst_flags_t complain) +{ + /* Unless users call the builtin directly, the following 2 checks should be + ensured from std::start_lifetime template. */ + if (nargs != 1) + { + if (complain & tf_error) + error_at (loc, "%<__builtin_start_lifetime%> needs a single " + "argument"); + return false; + } + tree arg = args[0]; + if (error_operand_p (arg)) + return false; + tree ptype = TREE_TYPE (arg); + if (!POINTER_TYPE_P (ptype)) + { + if (complain & tf_error) + error_at (loc, "%<__builtin_start_lifetime%> argument type %qT " + "is not pointer type", ptype); + return false; + } + tree type = TREE_TYPE (ptype); + if (!complete_type_or_else (type, NULL_TREE)) + return false; + if (!CP_AGGREGATE_TYPE_P (type)) + { + if (complain & tf_error) + error_at (loc, "%<__builtin_start_lifetime%> argument type %qT " + "is not a pointer to aggregate type", ptype); + return false; + } + if (!implicit_lifetime_type_p (type)) + { + if (complain & tf_error) + error_at (loc, "%<__builtin_start_lifetime%> argument type %qT " + "is not a pointer to implicit-lifetime type", ptype); + return false; + } + return true; +} + /* [basic.types] 8. True iff TYPE is an object type. */ static bool --- gcc/cp/constexpr.cc.jj 2026-05-20 08:43:07.244399392 +0200 +++ gcc/cp/constexpr.cc 2026-05-20 14:26:01.243438867 +0200 @@ -2456,6 +2456,49 @@ cxx_eval_constexpr_diag (const constexpr return void_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); + if (!check_builtin_start_lifetime (loc, call_expr_nargs (t), + call_expr_nargs (t) + ? &CALL_EXPR_ARG (t, 0) : NULL, + ctx->quiet ? tf_none + : tf_warning_or_error)) + { + *non_constant_p = true; + return 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; + tree modexpr = build2 (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. */ @@ -2529,6 +2572,10 @@ cxx_eval_builtin_function_call (const co return cxx_eval_constexpr_diag (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; } @@ -4708,7 +4755,8 @@ cxx_eval_call_expression (const constexp 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; @@ -4732,7 +4780,10 @@ 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) + field = NULL_TREE; + else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) { /* There must be a valid constant initializer at every array index. */ @@ -4766,12 +4817,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 @@ -4781,7 +4845,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. */ @@ -7817,7 +7882,7 @@ 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; @@ -7830,7 +7895,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 @@ -8037,6 +8102,11 @@ cxx_eval_store_expression (const constex const_object_being_modified = NULL_TREE; } + /* For __builtin_start_lifetime, if no union is involved, no need to + change anything. */ + if (start_lifetime && !seen_union) + return void_node; + type = TREE_TYPE (object); bool no_zero_init = true; bool zero_padding_bits = false; @@ -8170,12 +8240,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 @@ -8221,6 +8292,10 @@ cxx_eval_store_expression (const constex } no_zero_init = true; } + /* If this is the innermost union and we aren't changing active member + of it during start_lifetime, we don't need to change anything. */ + else if (code == UNION_TYPE && start_lifetime && seen_union == 1) + return void_node; /* Ending the lifetime of the active union member means the union no longer has an active member. */ --- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj 2026-05-20 08:43:02.579478077 +0200 +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-05-20 14:26:01.245053195 +0200 @@ -652,9 +652,9 @@ #elif __cpp_expansion_statements != 202506 # error "__cpp_expansion_statements != 202506" #endif #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 --- gcc/testsuite/g++.dg/cpp26/trivial-union3.C.jj 2026-05-20 14:26:01.245486713 +0200 +++ gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-05-20 14:26:01.245486713 +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); --- gcc/testsuite/g++.dg/cpp26/trivial-union4.C.jj 2026-05-20 14:26:01.245562152 +0200 +++ gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-05-20 14:26:01.245562152 +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; +} --- gcc/testsuite/g++.dg/cpp26/trivial-union5.C.jj 2026-05-20 14:26:01.245655416 +0200 +++ gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-05-20 14:26:01.245655416 +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" } +} --- gcc/testsuite/g++.dg/cpp26/trivial-union6.C.jj 2026-05-20 14:26:01.245756124 +0200 +++ gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-05-20 14:26:01.245756124 +0200 @@ -0,0 +1,26 @@ +// 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" "" { target c++23_down } } +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" "" { target c++23_down } } +constexpr D fD () { D a; a.a.b[0] = 1; return a; } +constexpr D d = fD (); // { dg-error "is not a constant expression" "" { target c++23_down } } +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" "" { target c++23_down } } +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" } --- libstdc++-v3/include/bits/version.def.jj 2026-05-20 08:43:02.584477993 +0200 +++ libstdc++-v3/include/bits/version.def 2026-05-20 14:26:01.249947842 +0200 @@ -2410,6 +2410,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"; --- libstdc++-v3/include/bits/version.h.jj 2026-05-20 08:43:02.584477993 +0200 +++ libstdc++-v3/include/bits/version.h 2026-05-20 14:26:01.250438751 +0200 @@ -2675,4 +2675,14 @@ #endif /* !defined(__cpp_lib_is_structural) */ #undef __glibcxx_want_is_structural +#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 --- libstdc++-v3/include/bits/stl_construct.h.jj 2026-03-27 10:17:22.883187971 +0100 +++ libstdc++-v3/include/bits/stl_construct.h 2026-05-20 14:30:52.025619611 +0200 @@ -404,6 +404,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 --- libstdc++-v3/include/std/memory.jj 2026-05-20 08:43:02.586477959 +0200 +++ libstdc++-v3/include/std/memory 2026-05-20 14:28:05.835373954 +0200 @@ -126,6 +126,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 --- libstdc++-v3/src/c++23/std.cc.in.jj 2026-05-20 08:43:02.591477875 +0200 +++ libstdc++-v3/src/c++23/std.cc.in 2026-05-20 14:26:01.251438735 +0200 @@ -2062,6 +2062,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
