[PATCH] c++, libstdc++: Implement C++26 P3074R7, P3726R0 and CWG3189 - trivial unions [PR119059]
Tomasz Kaminski
tkaminsk@redhat.com
Wed May 20 12:00:27 GMT 2026
On Wed, May 20, 2026 at 10:44 AM Jakub Jelinek <jakub@redhat.com> wrote:
> Hi!
>
> The following patch attempts to implement the C++26
> P3074R7 - trivial unions (was std::uninitialized<T>)
> P3726R2 - Adjustments to Union Lifetime Rules
> papers and proposed resolution of
> CWG3189 - Implicitly deleted destructors for union-like classes
> with the exception of the
> #define __cpp_lib_constexpr_inplace_vector 2025XXL // also in
> <inplace_vector>
> addition and possibly needed <inplace_vector> changes (will defer that to
> Jonathan / Tomasz).
>
> There is one change which doesn't affect just C++26 but also older versions
> of the standard, https://eel.is/c++draft/class.default.ctor#2.2 or its
> older
> counterparts, e.g. C++11 had in [class.ctor]/5
> "any non-variant non-static data member of const-qualified type (or array
> thereof)
> with no brace-or-equal-initializer does not have a user-provided default
> constructor"
> but we've been ignoring the "non-variant" part thereof and diagnosing it
> for variant members too. Note, this is related to the other unimplemented
> rule I've posted a patch earlier for that was dismissed (reject
> all variant members const before C++26), so some cases which we've rejected
> for a wrong reason will now be accepted when they are still invalid before
> C++26.
>
> For std::start_lifetime I've added a FE builtin which unlike the standard
> function template takes a pointer rather than reference, because it needs
> to
> be type-generic and for (...) it would attempt to pass a copy of the
> referenced object rather than the reference. As I didn't want to duplicate
> most of the cxx_eval_store_expression function for its constant evaluation,
> I've added an argument to that function and handle it as magic MODIFY_EXPR
> that in some cases doesn't do anything, but in other cases stores the
> CONSTRUCTOR_NO_CLEARING empty CONSTRUCTOR. The P3726R2 description of
> start_lifetime doesn't mention what to do when it is passed a reference to
> something which is not a constant expression (sometimes it could be
> within its lifetime, sometimes (e.g. when there is a union) we can't tell
> at constant evaluation time), or when it is one that didn't start lifetime
> during the evaluation of the core constant expression. The patch
> assumes it is valid to only handle stuff where the object actually could
> change lifetime. Another thing is const qualification. I believe even
> just changing the active union member is a change and so inappropriate in
> something that is const (unless during construction thereof), so the patch
> won't diagnose anything if all the right union members are active already,
> but if it would need to make some other union member active in something
> that is const, it will diagnose it.
>
> In reduced_constant_expression_p the patch also implements the second
> part of P3726R2, the changes to union elemental subobject handling, where
> it ignores holes for C++26 in arrays (possibly multi-dimensional) directly
> nested inside of union.
>
> There is one xfail in trivial-union6.C testcase,
> struct A { int a[2]; };
> constexpr A fA () { A a; a.a[0] = 1; return a; }
> constexpr A a = fA (); // { dg-error "is not a constant
> expression" "" { xfail *-*-* } }
> which is preexisting issue not actually related to unions, clang++ rejects
> this because there are holes in it (and for C++26 it is not union elemental
> subobject), but we happily just clear it in
> if (TREE_CODE (result) == CONSTRUCTOR
> && (cxx_dialect < cxx20
> || !DECL_CONSTRUCTOR_P (fun)))
> clear_no_implicit_zero (result);
> with no diagnostics.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
>
> 2026-05-20 Jakub Jelinek <jakub@redhat.com>
>
> gcc/c-family/
> * c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine
> __cpp_trivial_union to 202603L.
> gcc/cp/
> * method.cc: Implement C++26 P3074R7 - trivial unions (was
> std::uninitialized<T>), P3726R2 - Adjustments to Union Lifetime
> Rules
> and proposed resolution of CWG3189 - Implicitly deleted destructors
> for union-like classes.
> (walk_field_subobs): Don't do default_init_uninitialized_part
> checks
> for variant members. Don't check subobject ctor/dtor for variant
> members for ctor/inheriting ctor or when subobject doesn't have
> member
> initializer for dtor and it is either the dtor_from_ctor case or
> the current class doesn't have user provided ctors.
> * class.cc (check_field_decl): Don't or in
> TYPE_HAS_NONTRIVIAL_DESTRUCTOR or TYPE_HAS_DEFAULT_CONSTRUCTOR of
> variant subobjects for C++26.
> * cp-tree.h (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/DRs/dr2581-1.C: Expect warning for __cpp_trivial_union.
> * g++.dg/DRs/dr2581-2.C: Expect error for __cpp_trivial_union.
> * g++.dg/cpp26/feat-cxx26.C: Add __cpp_trivial_union checking.
> * g++.dg/cpp26/trivial-union1.C: New test.
> * g++.dg/cpp26/trivial-union2.C: New test.
> * 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/reflect/trivial-union1.C: New test.
> * g++.dg/reflect/type_trait6.C: Adjust expected result of
> one is_destructible_type and two is_nothrow_destructible_type
> calls.
> * g++.dg/reflect/is_constructible_type1.C: Adjust expected result
> of one is_constructible_type call.
> * g++.dg/init/pr43719.C: Don't expect one error.
> * g++.dg/init/pr25811.C: Don't expect 3 diagnostic messages,
> instead expect a different one for C++98 only.
> * g++.dg/other/anon-union2.C: Only expect one diagnostic for
> C++23 and older.
> * g++.dg/cpp0x/union1.C: Only expect 6 diagnostic messages for
> C++23 and older.
> * g++.dg/cpp0x/union4.C: Only expect 3 diagnostic messages for
> C++23 and older.
> * g++.dg/cpp0x/defaulted2.C: Only expect 2 diagnostic messages for
> C++23 and older.
> libstdc++-v3/
> * include/bits/version.def (start_lifetime): New.
> * include/bits/version.h: Regenerate.
> * include/std/memory: Define __glibcxx_want_start_lifetime before
> including bits/version.h.
> (std::start_lifetime): New function template.
> * src/c++23/std.cc.in: Add export std::start_lifetime.
>
> --- gcc/c-family/c-cppbuiltin.cc.jj 2026-05-19 09:11:42.796347298 +0200
> +++ gcc/c-family/c-cppbuiltin.cc 2026-05-19 14:49:03.999623920 +0200
> @@ -1122,6 +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=202603L");
> }
> if (flag_concepts && cxx_dialect > cxx14)
> cpp_define (pfile, "__cpp_concepts=202002L");
> --- gcc/cp/method.cc.jj 2026-05-19 09:11:42.825346810 +0200
> +++ gcc/cp/method.cc 2026-05-19 14:49:04.000032882 +0200
> @@ -2692,6 +2692,7 @@ walk_field_subobs (tree fields, special_
> enum { unknown, no, yes }
> only_dmi_mem = (sfk == sfk_constructor && TREE_CODE (ctx) == UNION_TYPE
> ? unknown : no);
> + int has_user_provided_ctor = -1;
>
> again:
> for (tree field = fields; field; field = DECL_CHAIN (field))
> @@ -2771,6 +2772,7 @@ walk_field_subobs (tree fields, special_
>
> bad = false;
> if (CP_TYPE_CONST_P (mem_type)
> + && TREE_CODE (ctx) != UNION_TYPE
> && default_init_uninitialized_part (mem_type))
> {
> if (diag)
> @@ -2847,6 +2849,22 @@ walk_field_subobs (tree fields, special_
> else
> argtype = NULL_TREE;
>
> + if (cxx_dialect >= cxx26 && TREE_CODE (ctx) == UNION_TYPE)
> + {
> + if (sfk == sfk_constructor || sfk == sfk_inheriting_constructor)
> + continue;
> +
> + if (sfk == sfk_destructor)
> + {
> + if (!dtor_from_ctor && has_user_provided_ctor == -1)
> + has_user_provided_ctor
> + = type_has_user_provided_constructor
> (current_class_type);
> + if (DECL_INITIAL (field) == NULL_TREE
> + && (dtor_from_ctor || !has_user_provided_ctor))
> + continue;
> + }
> + }
> +
> rval = locate_fn_flags (mem_type, fnname, argtype, flags, complain);
>
> process_subob_fn (rval, sfk, spec_p, trivial_p, deleted_p,
> --- gcc/cp/class.cc.jj 2026-05-19 09:11:42.800347231 +0200
> +++ gcc/cp/class.cc 2026-05-19 14:49:04.000697204 +0200
> @@ -3903,17 +3903,25 @@ check_field_decl (tree field,
> else
> {
> TYPE_NEEDS_CONSTRUCTING (t) |= TYPE_NEEDS_CONSTRUCTING (type);
> - TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t)
> - |= TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type);
> TYPE_HAS_COMPLEX_COPY_ASSIGN (t)
> |= (TYPE_HAS_COMPLEX_COPY_ASSIGN (type)
> || !TYPE_HAS_COPY_ASSIGN (type));
> TYPE_HAS_COMPLEX_COPY_CTOR (t) |= (TYPE_HAS_COMPLEX_COPY_CTOR
> (type)
> || !TYPE_HAS_COPY_CTOR
> (type));
> - TYPE_HAS_COMPLEX_MOVE_ASSIGN (t) |= TYPE_HAS_COMPLEX_MOVE_ASSIGN
> (type);
> + TYPE_HAS_COMPLEX_MOVE_ASSIGN (t)
> + |= TYPE_HAS_COMPLEX_MOVE_ASSIGN (type);
> TYPE_HAS_COMPLEX_MOVE_CTOR (t) |= TYPE_HAS_COMPLEX_MOVE_CTOR
> (type);
> - TYPE_HAS_COMPLEX_DFLT (t) |= (!TYPE_HAS_DEFAULT_CONSTRUCTOR
> (type)
> - || TYPE_HAS_COMPLEX_DFLT (type));
> + /* In C++26, triviality of default ctor or dtor of a variant
> member
> + doesn't matter for triviality of the t's default ctor or
> dtor. */
> + if (cxx_dialect < cxx26
> + || TREE_CODE (DECL_CONTEXT (field)) != UNION_TYPE)
> + {
> + TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t)
> + |= TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type);
> + TYPE_HAS_COMPLEX_DFLT (t)
> + |= (!TYPE_HAS_DEFAULT_CONSTRUCTOR (type)
> + || TYPE_HAS_COMPLEX_DFLT (type));
> + }
> }
>
> if (TYPE_HAS_COPY_CTOR (type)
> --- gcc/cp/cp-tree.h.jj 2026-05-19 09:11:42.814346995 +0200
> +++ gcc/cp/cp-tree.h 2026-05-19 17:24:56.329431575 +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
> };
>
> @@ -8665,6 +8666,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);
> @@ -9434,7 +9436,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-19 09:11:42.827346776 +0200
> +++ gcc/cp/tree.cc 2026-05-19 14:49:04.002359526 +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-19 09:11:42.818346928 +0200
> +++ gcc/cp/decl.cc 2026-05-19 14:49:04.003068157 +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-19 09:11:42.813347012 +0200
> +++ gcc/cp/cp-gimplify.cc 2026-05-19 16:47:38.464644177 +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-19 14:48:47.628341810 +0200
> +++ gcc/cp/semantics.cc 2026-05-19 14:49:04.005338408 +0200
> @@ -13984,6 +13984,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-19 09:11:48.004259554 +0200
> +++ gcc/cp/constexpr.cc 2026-05-19 17:35:17.969127198 +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. */
> @@ -7813,7 +7878,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;
>
> @@ -7826,7 +7891,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
> @@ -8033,6 +8098,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;
> @@ -8166,12 +8236,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
> @@ -8217,6 +8288,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/DRs/dr2581-1.C.jj 2026-05-19
> 09:11:42.840346557 +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2581-1.C 2026-05-19 14:49:04.007045168 +0200
> @@ -94,7 +94,7 @@
> #undef __cpp_template_parameters
> #undef __cpp_template_template_args // { dg-warning "undefining
> '__cpp_template_template_args'" "" { target c++20 } }
> #undef __cpp_threadsafe_static_init // { dg-warning "undefining
> '__cpp_threadsafe_static_init'" "" { target c++20 } }
> -#undef __cpp_trivial_union
> +#undef __cpp_trivial_union // { dg-warning "undefining
> '__cpp_trivial_union'" "" { target c++26 } }
> #undef __cpp_unicode_characters // { dg-warning
> "undefining '__cpp_unicode_characters'" "" { target c++20 } }
> #undef __cpp_unicode_literals // { dg-warning "undefining
> '__cpp_unicode_literals'" "" { target c++20 } }
> #undef __cpp_user_defined_literals // { dg-warning "undefining
> '__cpp_user_defined_literals'" "" { target c++20 } }
> --- gcc/testsuite/g++.dg/DRs/dr2581-2.C.jj 2026-05-19
> 09:11:42.840346557 +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2581-2.C 2026-05-19 14:49:04.007165066 +0200
> @@ -95,7 +95,7 @@
> #define __cpp_template_parameters 202502L
> #define __cpp_template_template_args 201611L // { dg-error
> "'__cpp_template_template_args' redefined" "" { target c++20 } }
> #define __cpp_threadsafe_static_init 200806L // { dg-error
> "'__cpp_threadsafe_static_init' redefined" "" { target c++20 } }
> -#define __cpp_trivial_union 202502L
> +#define __cpp_trivial_union 202603L // { dg-error
> "'__cpp_trivial_union' redefined" "" { target c++26 } }
> #define __cpp_unicode_characters 200704L // { dg-error
> "'__cpp_unicode_characters' redefined" "" { target c++17 } }
> #define __cpp_unicode_literals 200710L // { dg-error
> "'__cpp_unicode_literals' redefined" "" { target c++20 } }
> #define __cpp_user_defined_literals 200809L // { dg-error
> "'__cpp_user_defined_literals' redefined" "" { target c++20 } }
> --- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj 2026-05-19
> 09:11:42.844346490 +0200
> +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-05-19
> 14:49:04.007326066 +0200
> @@ -652,3 +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 != 202603
> +# error "__cpp_trivial_union != 202603"
> +#endif
> --- gcc/testsuite/g++.dg/cpp26/trivial-union1.C.jj 2026-05-19
> 14:49:04.007463254 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union1.C 2026-05-19
> 14:49:04.007463254 +0200
> @@ -0,0 +1,73 @@
> +// P3074R7 - trivial unions (was std::uninitialized<T>)
> +// P3726R2 - Adjustments to Union Lifetime Rules
> +// { dg-do compile { target c++11 } }
> +
> +#include <type_traits>
> +
> +// These two were incorrectly deleted.
> +union A { int a; const int b; };
> +static_assert (std::is_default_constructible <A>::value, "");
> +static_assert (std::is_trivially_default_constructible <A>::value, "");
> +static_assert (std::is_destructible <A>::value, "");
> +static_assert (std::is_trivially_destructible <A>::value, "");
> +struct B { int a; union { int b; const int c; }; };
> +static_assert (std::is_default_constructible <B>::value, "");
> +static_assert (std::is_trivially_default_constructible <B>::value, "");
> +static_assert (std::is_destructible <B>::value, "");
> +static_assert (std::is_trivially_destructible <B>::value, "");
> +// C::C() is incorrectly not deleted in C++11 to 23, but in C++26 it
> should
> +// not be deleted.
> +union C { const int a = 42; const long b; ~C (); };
> +#if __cpp_trivial_union >= 202502L
> +static_assert (std::is_default_constructible <C>::value, "");
> +static_assert (!std::is_trivially_default_constructible <C>::value, "");
> +static_assert (std::is_destructible <C>::value, "");
> +static_assert (!std::is_trivially_destructible <C>::value, "");
> +#endif
> +struct D { D () = delete; D (int); ~D () = default; };
> +union E { D a = 42; D b; ~E (); };
> +static_assert (std::is_default_constructible <E>::value, "");
> +static_assert (std::is_destructible <E>::value, "");
> +struct F { int a; union { D b = 42; D c; }; };
> +static_assert (std::is_default_constructible <F>::value, "");
> +static_assert (std::is_destructible <F>::value, "");
> +struct G { G (); ~G (); };
> +union I { int a; const int b; ~I (); };
> +static_assert (std::is_default_constructible <I>::value, "");
> +static_assert (!std::is_trivially_default_constructible <I>::value, "");
> +static_assert (std::is_destructible <I>::value, "");
> +static_assert (!std::is_trivially_destructible <I>::value, "");
> +union J { D a; int b; };
> +#if __cpp_trivial_union >= 202502L
> +static_assert (std::is_default_constructible <J>::value, "");
> +static_assert (std::is_trivially_default_constructible <J>::value, "");
> +#else
> +static_assert (!std::is_default_constructible <J>::value, "");
> +static_assert (!std::is_trivially_default_constructible <J>::value, "");
> +#endif
> +static_assert (std::is_destructible <J>::value, "");
> +static_assert (std::is_trivially_destructible <J>::value, "");
> +union K { G a; int b; };
> +#if __cpp_trivial_union >= 202502L
> +static_assert (std::is_default_constructible <K>::value, "");
> +static_assert (std::is_trivially_default_constructible <K>::value, "");
> +static_assert (std::is_destructible <K>::value, "");
> +static_assert (std::is_trivially_destructible <K>::value, "");
> +#else
> +static_assert (!std::is_default_constructible <K>::value, "");
> +static_assert (!std::is_trivially_default_constructible <K>::value, "");
> +static_assert (!std::is_destructible <K>::value, "");
> +static_assert (!std::is_trivially_destructible <K>::value, "");
> +#endif
> +struct L { int a; union { G b; int c; }; };
> +#if __cpp_trivial_union >= 202502L
> +static_assert (std::is_default_constructible <L>::value, "");
> +static_assert (std::is_trivially_default_constructible <L>::value, "");
> +static_assert (std::is_destructible <L>::value, "");
> +static_assert (std::is_trivially_destructible <L>::value, "");
> +#else
> +static_assert (!std::is_default_constructible <L>::value, "");
> +static_assert (!std::is_trivially_default_constructible <L>::value, "");
> +static_assert (!std::is_destructible <L>::value, "");
> +static_assert (!std::is_trivially_destructible <L>::value, "");
> +#endif
>
I would like to see test user-provided constructors for union-like types,
something like:
union U { U(); int x; X b; }; // trivial for X = int, and deleted for T
= G (non-trivial destructor)
struct UL { UL(); union { int x; X b; } }; // same
Also, for a non-default constructor and a union-like version of the above
union U { U(int); int x; X b; }; // All constructors matter, not only the
default one,
union U { U() = default; U(int); int x; X b; }; // all cosntructor
matters, not only default,
(above with union-like versions)
> --- gcc/testsuite/g++.dg/cpp26/trivial-union2.C.jj 2026-05-19
> 15:40:23.361611939 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union2.C 2026-05-19
> 16:05:33.305490645 +0200
> @@ -0,0 +1,8 @@
> +// P3074R7 - trivial unions (was std::uninitialized<T>)
> +// P3726R2 - Adjustments to Union Lifetime Rules
> +// { dg-do compile { target c++20 } }
> +
> +union U { int a, b; };
> +template<U u> class X {};
> +constexpr U make() { U u; return u; }
> +void f(X<make()>) {}
> --- gcc/testsuite/g++.dg/cpp26/trivial-union3.C.jj 2026-05-19
> 15:44:47.009211069 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-05-19
> 15:46:55.055074118 +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;
> +}
>
This would be a follow-up regardless, but could you check if codegen for
the below
is now the same:
FixedVector<char, 4> f() {
return {'a', 'b', 'c'};
}
FixedVector<char, 4> g() {
constexpr FixedVector<char, 4> result{'a', 'b', 'c'};
return result;
}
(Need a FixedVector from initializer_liust constructor )
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124121
> +
> +static_assert (silly_test () == 1);
> --- gcc/testsuite/g++.dg/cpp26/trivial-union4.C.jj 2026-05-19
> 16:38:43.464464194 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-05-19
> 16:41:51.769423861 +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-19
> 16:42:08.294148384 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-05-19
> 17:16:15.760061082 +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-19
> 17:54:58.353551284 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-05-19
> 18:00:19.595221493 +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 } }
>
I think it's already mentioned in the commit message, but all of the above
should also fails in C++26 mode.
The assigments rules were not changed so a.b[0][0] = 1, starts the whole
array with all the elements,
and we have uninitialized elements inside, that are in-lifetime.
To achieve the above, you should start_lifetime of the array first and only
then assign values to its elements,
so https://eel.is/c++draft/class.union#general-5 does not create an array
with elements.
> +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" }
> --- gcc/testsuite/g++.dg/reflect/trivial-union1.C.jj 2026-05-19
> 14:49:04.007691969 +0200
> +++ gcc/testsuite/g++.dg/reflect/trivial-union1.C 2026-05-19
> 14:49:04.007691969 +0200
> @@ -0,0 +1,108 @@
> +// P3074R7 - trivial unions (was std::uninitialized<T>)
> +// P3726R2 - Adjustments to Union Lifetime Rules
> +// { dg-do compile { target c++26 } }
> +// { dg-additional-options "-freflection" }
> +
> +#include <meta>
> +#include <ranges>
> +
> +using namespace std::meta;
> +constexpr auto ctx = std::meta::access_context::unchecked ();
> +union A { int a; const int b; };
> +static_assert (is_default_constructible_type (^^A));
> +static_assert (is_trivially_default_constructible_type (^^A));
> +static_assert (is_destructible_type (^^A));
> +static_assert (is_trivially_destructible_type (^^A));
> +constexpr auto Actor = (members_of (^^A, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Adtor = (members_of (^^A, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Actor) && !is_deleted (Actor));
> +static_assert (is_defaulted (Adtor) && !is_deleted (Adtor));
> +struct B { int a; union { int b; const int c; }; };
> +static_assert (is_default_constructible_type (^^B));
> +static_assert (is_trivially_default_constructible_type (^^B));
> +static_assert (is_destructible_type (^^B));
> +static_assert (is_trivially_destructible_type (^^B));
> +constexpr auto Bctor = (members_of (^^B, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Bdtor = (members_of (^^B, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Bctor) && !is_deleted (Bctor));
> +static_assert (is_defaulted (Bdtor) && !is_deleted (Bdtor));
> +union C { const int a = 42; const long b; ~C (); };
> +static_assert (is_default_constructible_type (^^C));
> +static_assert (!is_trivially_default_constructible_type (^^C));
> +static_assert (is_destructible_type (^^C));
> +static_assert (!is_trivially_destructible_type (^^C));
> +constexpr auto Cctor = (members_of (^^C, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Cdtor = (members_of (^^C, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Cctor) && !is_deleted (Cctor));
> +static_assert (!is_defaulted (Cdtor) && !is_deleted (Cdtor));
> +struct D { D () = delete; D (int); ~D () = default; };
> +union E { D a = 42; D b; ~E (); };
> +static_assert (is_default_constructible_type (^^E));
> +static_assert (is_destructible_type (^^E));
> +constexpr auto Ector = (members_of (^^E, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Edtor = (members_of (^^E, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Ector) && !is_deleted (Ector));
> +static_assert (!is_defaulted (Edtor) && !is_deleted (Edtor));
> +struct F { int a; union { D b = 42; D c; }; };
> +static_assert (is_default_constructible_type (^^F));
> +static_assert (is_destructible_type (^^F));
> +constexpr auto Fctor = (members_of (^^F, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Fdtor = (members_of (^^F, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Fctor) && !is_deleted (Fctor));
> +static_assert (is_defaulted (Fdtor) && !is_deleted (Fdtor));
> +struct G { G (); ~G (); };
> +union I { int a; const int b; ~I (); };
> +static_assert (is_default_constructible_type (^^I));
> +static_assert (!is_trivially_default_constructible_type (^^I));
> +static_assert (is_destructible_type (^^I));
> +static_assert (!is_trivially_destructible_type (^^I));
> +constexpr auto Ictor = (members_of (^^I, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Idtor = (members_of (^^I, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Ictor) && !is_deleted (Ictor));
> +static_assert (!is_defaulted (Idtor) && !is_deleted (Idtor));
> +union J { D a; int b; };
> +static_assert (is_default_constructible_type (^^J));
> +static_assert (is_trivially_default_constructible_type (^^J));
> +static_assert (is_destructible_type (^^J));
> +static_assert (is_trivially_destructible_type (^^J));
> +constexpr auto Jctor = (members_of (^^J, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Jdtor = (members_of (^^J, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Jctor) && !is_deleted (Jctor));
> +static_assert (is_defaulted (Jdtor) && !is_deleted (Jdtor));
> +union K { G a; int b; };
> +static_assert (is_default_constructible_type (^^K));
> +static_assert (is_trivially_default_constructible_type (^^K));
> +static_assert (is_destructible_type (^^K));
> +static_assert (is_trivially_destructible_type (^^K));
> +constexpr auto Kctor = (members_of (^^K, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Kdtor = (members_of (^^K, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Kctor) && !is_deleted (Kctor));
> +static_assert (is_defaulted (Kdtor) && !is_deleted (Kdtor));
> +struct L { int a; union { G b; int c; }; };
> +static_assert (is_default_constructible_type (^^L));
> +static_assert (is_trivially_default_constructible_type (^^L));
> +static_assert (is_destructible_type (^^L));
> +static_assert (is_trivially_destructible_type (^^L));
> +constexpr auto Lctor = (members_of (^^L, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Ldtor = (members_of (^^L, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (is_defaulted (Lctor) && !is_deleted (Lctor));
> +static_assert (is_defaulted (Ldtor) && !is_deleted (Ldtor));
> +union M { M (); int a; long b; };
> +static_assert (is_default_constructible_type (^^M));
> +static_assert (!is_trivially_default_constructible_type (^^M));
> +static_assert (is_destructible_type (^^M));
> +static_assert (is_trivially_destructible_type (^^M));
> +constexpr auto Mctor = (members_of (^^M, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Mdtor = (members_of (^^M, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
> +static_assert (!is_defaulted (Mctor) && !is_deleted (Mctor));
> +static_assert (is_defaulted (Mdtor) && !is_deleted (Mdtor));
> +struct N { N () = default; N (const N &) = default; int a; ~N (); };
> +union O { O (); int a; N b; };
> +static_assert (!is_default_constructible_type (^^O));
> +static_assert (!is_trivially_default_constructible_type (^^O));
> +static_assert (!is_destructible_type (^^O));
> +static_assert (!is_trivially_destructible_type (^^O));
> +constexpr auto Octor = (members_of (^^O, ctx) | std::views::filter
> (is_default_constructor) | std::ranges::to <std::vector> ())[0];
> +constexpr auto Odtor = (members_of (^^O, ctx) | std::views::filter
> (is_destructor) | std::ranges::to <std::vector> ())[0];
+static_assert (!is_defaulted (Octor) && !is_deleted (Octor));
> +static_assert (is_defaulted (Odtor) && is_deleted (Odtor));
>
I assume you are skipping questions about the constructor/destructor
anonymous union inside
union-like types on purpose?
> --- gcc/testsuite/g++.dg/reflect/type_trait6.C.jj 2026-05-19
> 09:11:42.847346439 +0200
> +++ gcc/testsuite/g++.dg/reflect/type_trait6.C 2026-05-19
> 14:49:04.007837480 +0200
> @@ -985,7 +985,7 @@ static_assert (!is_destructible_type (^^
> static_assert (!is_destructible_type (^^const N2::Del [1]));
> static_assert (!is_destructible_type (^^N2::Del []));
> static_assert (!is_destructible_type (^^const N2::Del []));
> -static_assert (!is_destructible_type (^^N2::NontrivialUnion));
> +static_assert (is_destructible_type (^^N2::NontrivialUnion));
> static_assert (is_destructible_type (^^N2::UnusualCopy));
>
> static_assert (is_trivially_default_constructible_type (^^int));
> @@ -1367,8 +1367,8 @@ static_assert (!is_nothrow_destructible_
> static_assert (!is_nothrow_destructible_type (^^N2::Aggr2));
> static_assert (!is_nothrow_destructible_type (^^N2::Aggr2 [1]));
> static_assert (!is_nothrow_destructible_type (^^N2::TD1 [1][2]));
> -static_assert (!is_nothrow_destructible_type (^^N2::Ut));
> -static_assert (!is_nothrow_destructible_type (^^N2::Ut [3]));
> +static_assert (is_nothrow_destructible_type (^^N2::Ut));
> +static_assert (is_nothrow_destructible_type (^^N2::Ut [3]));
> static_assert (!is_nothrow_destructible_type (^^N2::AbstractDelDtor));
> static_assert (!is_nothrow_destructible_type (^^N2::Abstract2));
> static_assert (!is_nothrow_destructible_type (^^N2::Abstract3));
> --- gcc/testsuite/g++.dg/reflect/is_constructible_type1.C.jj 2026-05-19
> 09:11:42.847346439 +0200
> +++ gcc/testsuite/g++.dg/reflect/is_constructible_type1.C 2026-05-19
> 14:49:04.008086954 +0200
> @@ -603,7 +603,7 @@ static_assert (!is_constructible_type (^
> static_assert (!is_constructible_type (^^const DelnAny, { ^^int, ^^void *
> }));
> static_assert (!is_constructible_type (^^DelnAny, { ^^Empty, ^^B, ^^D }));
> static_assert (!is_constructible_type (^^const DelnAny, { ^^Empty, ^^B,
> ^^D }));
> -static_assert (!is_constructible_type (^^NontrivialUnion, {}));
> +static_assert (is_constructible_type (^^NontrivialUnion, {}));
> static_assert (!is_constructible_type (^^NontrivialUnion, { ^^const
> NontrivialUnion & }));
> static_assert (!is_constructible_type (^^UnusualCopy, {}));
> static_assert (!is_constructible_type (^^UnusualCopy, { ^^UnusualCopy }));
> --- gcc/testsuite/g++.dg/init/pr43719.C.jj 2026-04-29
> 23:30:26.794629097 +0200
> +++ gcc/testsuite/g++.dg/init/pr43719.C 2026-05-20 09:39:15.124693366 +0200
> @@ -109,7 +109,7 @@ struct Z // { dg-error "deleted" "" { t
> Z5 z5;
> };
>
> -union U // { dg-error "uninitialized" "" { target c++11 } }
> +union U
> {
> int const i; // { dg-message "should be initialized" }
> };
> --- gcc/testsuite/g++.dg/init/pr25811.C.jj 2026-04-29
> 23:30:26.794629097 +0200
> +++ gcc/testsuite/g++.dg/init/pr25811.C 2026-05-20 09:46:05.297786524 +0200
> @@ -124,10 +124,9 @@ struct Z // { dg-error "deleted" "" { ta
> Z5 z5;
> };
>
> -union U // { dg-message "implicitly deleted" "" { target c++11 } }
> - // { dg-error "uninitialized" "" { target c++11 } .-1 }
> +union U
> {
> - int const i; // { dg-message "should be initialized" }
> + int const i;
> };
>
> void f1 ()
> @@ -207,5 +206,5 @@ void f15 ()
>
> void f16 ()
> {
> - new U; // { dg-error "deleted|uninitialized const member" }
> + new U; // { dg-error "uninitialized const member in 'union U' using
> 'new' without new-initializer" "" { target c++98_only } }
> }
> --- gcc/testsuite/g++.dg/other/anon-union2.C.jj 2020-01-14
> 20:02:46.897608126 +0100
> +++ gcc/testsuite/g++.dg/other/anon-union2.C 2026-05-20
> 09:41:12.450717732 +0200
> @@ -6,5 +6,5 @@ struct S {
> };
>
> void f() {
> - union { S a; }; // { dg-error "constructor|no match" }
> + union { S a; }; // { dg-error "constructor|no match" "" {
> target c++23_down } }
> }
> --- gcc/testsuite/g++.dg/cpp0x/union1.C.jj 2020-01-14
> 20:02:46.761610163 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/union1.C 2026-05-20 09:44:52.092019226 +0200
> @@ -14,7 +14,7 @@ union B
> A a; // { dg-error "union member" }
> };
>
> -B b; // { dg-error "B::B\\(\\)" "B::B" }
> +B b; // { dg-error "B::B\\(\\)" "B::B" { target
> c++23_down } }
> B b2(b); // { dg-error "B::B\\(const B&\\)" "B::B" }
>
> struct C
> @@ -25,10 +25,10 @@ struct C
> };
> };
>
> -C c; // { dg-error "C::C\\(\\)" "C::C" }
> +C c; // { dg-error "C::C\\(\\)" "C::C" { target
> c++23_down } }
> C c2(c); // { dg-error "C::C\\(const C&\\)" "C::C" }
>
> -// { dg-error "B::~B" "B::~B" { target *-*-* } 17 }
> -// { dg-error "B::~B" "B::~B" { target *-*-* } 18 }
> -// { dg-error "C::~C" "C::~C" { target *-*-* } 28 }
> -// { dg-error "C::~C" "C::~C" { target *-*-* } 29 }
> +// { dg-error "B::~B" "B::~B" { target c++23_down } 17 }
> +// { dg-error "B::~B" "B::~B" { target c++23_down } 18 }
> +// { dg-error "C::~C" "C::~C" { target c++23_down } 28 }
> +// { dg-error "C::~C" "C::~C" { target c++23_down } 29 }
> --- gcc/testsuite/g++.dg/cpp0x/union4.C.jj 2020-01-14
> 20:02:46.761610163 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/union4.C 2026-05-20 09:33:12.192804729 +0200
> @@ -3,15 +3,15 @@
>
> struct SFoo
> {
> - SFoo() =delete; // { dg-message "declared" }
> + SFoo() =delete; // { dg-message "declared" "" { target
> c++23_down } }
> };
>
> -union UFoo // { dg-error "deleted" }
> +union UFoo // { dg-error "deleted" "" { target
> c++23_down } }
> {
> SFoo foo;
> };
>
> int main()
> {
> - UFoo(); // { dg-error "deleted" }
> + UFoo(); // { dg-error "deleted" "" { target
> c++23_down } }
> }
> --- gcc/testsuite/g++.dg/cpp0x/defaulted2.C.jj 2020-01-14
> 20:02:46.722610747 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/defaulted2.C 2026-05-20
> 09:31:00.031028888 +0200
> @@ -55,7 +55,7 @@ G::G() = default;
>
> union U
> {
> - G g; // { dg-error "union member.*non-trivial" }
> + G g; // { dg-error "union member.*non-trivial"
> "" { target c++23_down } }
> };
>
> int main()
> @@ -63,7 +63,7 @@ int main()
> F f;
> F f2(f); // { dg-error "use" }
> const B* b = new const B; // { dg-error "uninitialized
> const" }
> - U u; // { dg-error "deleted" }
> + U u; // { dg-error "deleted" "" { target
> c++23_down } }
> }
>
> // { dg-prune-output "implicitly deleted because" }
> --- libstdc++-v3/include/bits/version.def.jj 2026-05-19
> 09:11:42.851346372 +0200
> +++ libstdc++-v3/include/bits/version.def 2026-05-19
> 14:49:04.008291289 +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-19
> 09:11:42.851346372 +0200
> +++ libstdc++-v3/include/bits/version.h 2026-05-19 14:49:04.008536034 +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/std/memory.jj 2026-05-19 09:11:42.854346321 +0200
> +++ libstdc++-v3/include/std/memory 2026-05-19 16:08:08.038949038 +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
> @@ -175,6 +176,28 @@ _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace
> #endif // C++11 to C++20
>
> +#if __glibcxx_start_lifetime >= 202603L // C++ >= 26
> +namespace std _GLIBCXX_VISIBILITY(default)
> +{
> +_GLIBCXX_BEGIN_NAMESPACE_VERSION
> +
> + template <class _Tp>
> + constexpr void
> + start_lifetime(_Tp& __r) noexcept
>
Could you move the declaration to libstdc++-v3/include/bits/stl_construct.h,
(we start_lifetime_as, cosntruct_as is). We do not not want to include
whole header.
> + {
> +#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));
> + }
> +
> +_GLIBCXX_END_NAMESPACE_VERSION
> +} // namespace
> +#endif
> +
> #ifdef __cpp_lib_parallel_algorithm // C++ >= 17 && HOSTED
> // Parallel STL algorithms
> # if _PSTL_EXECUTION_POLICIES_DEFINED
> --- libstdc++-v3/src/c++23/std.cc.in.jj 2026-04-22 15:03:36.487701193 +0200
> +++ libstdc++-v3/src/c++23/std.cc.in 2026-05-19 16:17:58.239126126
> +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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260520/cd792afe/attachment-0001.htm>
More information about the Libstdc++
mailing list