[PATCH] c++, libstdc++, v2: Implement C++26 P3074R7, P3726R0 and CWG3189 - trivial unions [PR119059]

Tomasz Kaminski tkaminsk@redhat.com
Thu May 21 06:37:46 GMT 2026


On Wed, May 20, 2026 at 5:41 PM Jakub Jelinek <jakub@redhat.com> wrote:

> On Wed, May 20, 2026 at 02:00:27PM +0200, Tomasz Kaminski wrote:
> > 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)
>
> I've added some tests.
>
> > 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
>
> The FixedVector doesn't have ctor from initializer_list; when using
> inplace_vector as is in the headers, f gets worse code generated,
> e.g. because start_lifetime_as uses inline asm.
>
> > > +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.
>
> Haven't touched this yet.
> >
> > +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?
>
> CWG3130 says they don't exist and I plan to work on resolving that soon,
> so trying to test those is pointless.
>
> > > +#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.
>
> Moved.
>
> 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/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,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-20 08:43:02.566478296 +0200
> +++ gcc/cp/method.cc    2026-05-20 14:26:01.234439016 +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-20 08:43:02.551478549 +0200
> +++ gcc/cp/class.cc     2026-05-20 14:26:01.235439000 +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-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/DRs/dr2581-1.C.jj      2026-05-20
> 08:43:02.577478111 +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2581-1.C 2026-05-20 14:26:01.244438851 +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-20
> 08:43:02.577478111 +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2581-2.C 2026-05-20 14:26:01.244751680 +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/cpp0x/union1.C.jj      2026-03-27
> 10:17:15.542307763 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/union1.C 2026-05-20 14:26:01.248075538 +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      2026-03-27
> 10:17:15.542307763 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/union4.C 2026-05-20 14:26:01.248438784 +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  2026-03-27
> 10:17:15.446309330 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/defaulted2.C     2026-05-20
> 14:26:01.248646182 +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" }
> --- 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,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-20
> 14:26:01.245197174 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union1.C 2026-05-20
> 17:26:39.297942500 +0200
> @@ -0,0 +1,93 @@
> +// 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
> +union M { M (); int a; int b; };
> +static_assert (std::is_default_constructible <M>::value, "");
> +static_assert (!std::is_trivially_default_constructible <M>::value, "");
> +static_assert (std::is_destructible <M>::value, "");
> +static_assert (std::is_trivially_destructible <M>::value, "");
> +union N { N (); int a; G b; };
> +static_assert (!std::is_default_constructible <N>::value, "");
> +static_assert (!std::is_trivially_default_constructible <N>::value, "");
> +static_assert (!std::is_destructible <N>::value, "");
> +static_assert (!std::is_trivially_destructible <N>::value, "");
> +struct O { O (); union { int a; int b; }; };
> +static_assert (std::is_default_constructible <O>::value, "");
> +static_assert (!std::is_trivially_default_constructible <O>::value, "");
> +static_assert (std::is_destructible <O>::value, "");
> +static_assert (std::is_trivially_destructible <O>::value, "");
> +struct P { P (); union { int a; G b; }; };
> +static_assert (!std::is_default_constructible <P>::value, "");
> +static_assert (!std::is_trivially_default_constructible <P>::value, "");
> +static_assert (!std::is_destructible <P>::value, "");
> +static_assert (!std::is_trivially_destructible <P>::value, "");
>
I would still like to see cases where the default constructor is not the
one creating the union.
Destructor deleted.
struct R { R () = default; R(int); union { int a; G b; }; };
static_assert (std::is_default_constructible <R>::value, "");
static_assert (std::is_trivially_default_constructible <R>::value, "");
static_assert (!std::is_destructible <R>::value, "");
static_assert (!std::is_trivially_destructible <R>::value, "");

struct Q { Q(int); union { int a; G b; }; };
static_assert (!std::is_default_constructible <Q>::value, "");
static_assert (!std::is_trivially_default_constructible <Q>::value, "");
static_assert (!std::is_destructible <Q>::value, "");
static_assert (!std::is_trivially_destructible <Q>::value, "");

Have you considered using templates for this tests, to have something like
this?
I will find that easier to follow, but it does not seem to be used in the
core tests,
so feel free to ignore that suggestions. (Unions could also be tested this
way).

template<bool XTrivial, typename X>
void test_union_like() {
   struct A { union { int a; X 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 { union { int a; X b = 10; }; };
   static_assert (std::is_default_constructible <B>::value, "");
   static_assert (!std::is_trivially_default_constructible <B>::value, "");
   static_assert (std::is_destructible <B>::value == XTrivial, "");
   static_assert (std::is_trivially_destructible <B>::value == XTrivial,
"");

   struct C { C(); union { int a; X b = 10; }; };
   static_assert (std::is_default_constructible <C>::value, "");
   static_assert (!std::is_trivially_default_constructible <C>::value, "");
   static_assert (std::is_destructible <C>::value == XTrivial, "");
   static_assert (std::is_trivially_destructible <C>::value == XTrivial,
"");

   struct D { D() = default; D(int); union { int a; X b = 10; }; };
   static_assert (std::is_default_constructible <D>::value, "");
   static_assert (std::is_trivially_default_constructible <D>::value, "");
   static_assert (std::is_destructible <D>::value == XTrivial, "");
   static_assert (std::is_trivially_destructible <D>::value == XTrivial,
"");

   // Defaullt constructor not present
   struct E { E(int); union { int a; X b = 10; }; };
   static_assert (!std::is_default_constructible <E>::value, "");
   static_assert (!std::is_trivially_default_constructible <E>::value, "");
   static_assert (std::is_destructible <E>::value == XTrivial, "");
   static_assert (std::is_trivially_destructible <E>::value == XTrivial,
"");
}

struct NT { NT(int); ~NT(); };

int main() {
   test_union_like<true, int>;
   test_union_like<true, NT>;
}



> --- gcc/testsuite/g++.dg/cpp26/trivial-union2.C.jj      2026-05-20
> 14:26:01.245359408 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union2.C 2026-05-20
> 14:26:01.245359408 +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-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" }
> --- gcc/testsuite/g++.dg/init/pr43719.C.jj      2026-04-29
> 07:45:23.186351218 +0200
> +++ gcc/testsuite/g++.dg/init/pr43719.C 2026-05-20 14:26:01.247279549 +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
> 07:45:23.183351272 +0200
> +++ gcc/testsuite/g++.dg/init/pr25811.C 2026-05-20 14:26:01.247438801 +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 2026-03-27
> 10:17:16.040299637 +0100
> +++ gcc/testsuite/g++.dg/other/anon-union2.C    2026-05-20
> 14:26:01.247698101 +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/reflect/trivial-union1.C.jj    2026-05-20
> 14:26:01.245919804 +0200
> +++ gcc/testsuite/g++.dg/reflect/trivial-union1.C       2026-05-20
> 14:26:01.245919804 +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));
> --- gcc/testsuite/g++.dg/reflect/type_trait6.C.jj       2026-05-20
> 08:43:02.581478043 +0200
> +++ gcc/testsuite/g++.dg/reflect/type_trait6.C  2026-05-20
> 14:26:01.246323664 +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-20
> 08:43:02.580478060 +0200
> +++ gcc/testsuite/g++.dg/reflect/is_constructible_type1.C       2026-05-20
> 14:26:01.246691288 +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 }));
> --- 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260521/1ad40175/attachment-0001.htm>


More information about the Libstdc++ mailing list