Hi Jakub,

Just lurking over here... No expert or anything, so here's a pile of salt:
'(a-pile-of-salt)

On Mon, May 4, 2026 at 7:41 AM Jakub Jelinek <[email protected]> wrote:

> Hi!
>
> So, I've started working on the P3074R7 trivial unions paper except for
> that [class.default.ctor]/4 long sentence that P3726R2 removed again.
> I hope I'm missing something, but the more I look into this the more
> problems I see.
>
> The paper contains:
> "which wasn’t particularly surprising since this change is entirely about
> making
> existing ill-formed code valid" but that doesn't seem to be the case
> to me, see below.
>
> One small nit first, I don't understand e.g. the
> https://eel.is/c++draft/class.default.ctor#2.2 addition of
> "X is a non-union class and", because the bullet only talks about
> non-variant non-static data members, aren't all non-static data members
> in a union already variant members and so the addition doesn't change
> anything?
>

I believe that is for those cases where X is a non-union class, but
contains an anonymous union. The members of X not within an anonymous union
would be those non-variant non-static data members referred in that clause.
Like:

```
class Y {
  int m_i;
  Y() = delete;
  Y(int i) : m_i(i) {}
};

class X1 {
   union { int a; };
}; // default constructor NOT defined as deleted

class X2 {
   Y y;
   union { int a; };
}; // default constructor defined as deleted, due to class Y not being
const-default-constructible
```


> More importantly, defaulted dtor would be previously deleted if it had some
> variant subobject with deleted or inaccessible or non-trivial dtor, my
> understanding is that the paper wants to intentionally limit that to
> variant
> members with default initializer, but
> https://eel.is/c++draft/class.mem#class.dtor-7.2.2
> doesn't seem to do that.  For one, it doesn't apply to variant members in
> anonymous unions because it only talks about union classes, so I believe
> struct A { A (int); ~A () = delete; int a; };
> struct B { union { A a = 42; int b; }; };
> B::~B () per the new rules isn't deleted.  And for unions, guess whether
>

I believe that's because due to
https://eel.is/c++draft/class.default.ctor#2.4
Because "a" has a default member initializer and its class A has a deleted
destructor,
then the default constructor of B is defined as deleted. So you can't
construct it, and so
there's no point in deleting it's destructor. As there's no way to start
it's lifetime, there's no way
to end it's lifetime anyway.


> the subobject with default initializer and deleted dtor is now covered by
> https://eel.is/c++draft/class.mem#class.dtor-7.2.1 because in that case
> the ctor due to https://eel.is/c++draft/class.default.ctor#2.4 will be
> deleted, but that checks for accessibility of the subobject dtor in the
> default ctor, not in the dtor.
>

I don't believe https://eel.is/c++draft/class.mem#class.dtor-7.2.1 applies,
as B is not a union.
It might be union-like, but technically not a union.


>
> Most importantly, the https://eel.is/c++draft/class.mem#class.dtor-7.2.1
> new rule doesn't seem to match the
> "entirely about making existing ill-formed code valid" comment, I believe
> union C { int a = 42; long b; } u;
> used to be valid in C++11 to C++23, but in my understanding is now invalid.
> There is a variant member, so
> https://eel.is/c++draft/class.default.ctor#3.2
> says that C::C () is non-trivial, and then
> https://eel.is/c++draft/class.mem#class.dtor-7.2.1 says that if the
> default
> ctor is non-trivial, defaulted C::~C () is defined as deleted.
> Was that intentional?
>
> The patch below drops the default_init_uninitialized_part check in
> all variant members because already C++11 had there the non-variant case,
> the second hunk doesn't implement the paper exactly as written (see the
> more importantly point above), as it doesn't differentiate between the
> sfk_destructor case due to dtor_from_ctor (that would look like what
> it writes right now) but if !dtor_from_ctor with the current wording it
> would need to limit itself just to the toplevel unions (not anonymous
> unions) and somehow check if the corresponding subobj dtor is non-trivial
> without checking if it is deleted or doesn't exist (how?).
>
> The synthesized_method_walk hunk I believe implements
> https://eel.is/c++draft/class.mem#class.dtor-7.2.1 as written with the
> above mentioned implications.
>
> Regarding P3726R2, guess we want some type generic builtin
> (__builtin_start_lifetime ?) which will be used in std::start_lifetime
> template and at constant evaluation time will set/switch active union
> member (what should happen if some other union member is active and there
> is non-trivial dtor?).  Plus some tweaks on what is valid constant
> expression for C++26 in (possibly multi-dimensional) arrays inside of
> unions.
>
> --- gcc/c-family/c-cppbuiltin.cc.jj     2026-04-22 15:03:36.485207851 +0200
> +++ gcc/c-family/c-cppbuiltin.cc        2026-05-04 11:13:26.546729223 +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-04 09:08:58.427717395 +0200
> +++ gcc/cp/method.cc    2026-05-04 12:33:01.324557261 +0200
> @@ -2771,6 +2771,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 +2848,15 @@ 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 && DECL_INITIAL (field) == NULL_TREE)
> +           continue;
> +       }
> +
>        rval = locate_fn_flags (mem_type, fnname, argtype, flags, complain);
>
>        process_subob_fn (rval, sfk, spec_p, trivial_p, deleted_p,
> @@ -2985,6 +2995,35 @@ synthesized_method_walk (tree ctype, spe
>        /* The synthesized method will call base dtors, but check complete
>          here to avoid having to deal with VTT.  */
>        fnname = complete_dtor_identifier;
> +
> +      if (TREE_CODE (ctype) == UNION_TYPE
> +         && cxx_dialect >= cxx26
> +         && deleted_p)
> +       {
> +         /* [class.dtor]/(7.2.1):
> +            A defaulted destructor for a class X is defined as deleted
> if  X
> +            is a union and
> +            -- overload resolution to select a constructor to
> +               default-initialize an object of type X either fails or
> selects
> +               a constructor that is either deleted or not trivial.  */
> +         tree ctor = locate_ctor (ctype);
> +         if (ctor == NULL_TREE)
> +           {
> +             *deleted_p = true;
> +             if (diag)
> +               inform (DECL_SOURCE_LOCATION (TYPE_NAME (ctype)),
> +                       "default constructor of %qT unusable", ctype);
> +             return;
> +           }
> +         else if (!trivial_fn_p (ctor))
> +           {
> +             *deleted_p = true;
> +             if (diag)
> +               inform (DECL_SOURCE_LOCATION (TYPE_NAME (ctype)),
> +                       "default constructor of %qT is not trivial",
> ctype);
> +             return;
> +           }
> +       }
>      }
>    else if (SFK_ASSIGN_P (sfk))
>      fnname = assign_op_identifier;
> --- gcc/testsuite/g++.dg/DRs/dr2581-1.C.jj      2026-03-27
> 10:17:15.320311386 +0100
> +++ gcc/testsuite/g++.dg/DRs/dr2581-1.C 2026-05-04 11:15:02.107141015 +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-04-22
> 15:03:36.485585053 +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2581-2.C 2026-05-04 11:15:29.961678074 +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-03-27
> 10:17:15.654305935 +0100
> +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C     2026-05-04
> 11:14:08.680028970 +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-04
> 11:17:47.404393790 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-union1.C 2026-05-04
> 12:43:42.852056550 +0200
> @@ -0,0 +1,37 @@
> +// 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, except for C++26
> +// where the dtor is 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, "");
> +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, "");
> +// 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, "");
> +#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, "");
> +struct F { int a; union { D b = 42; D c; }; };
> +static_assert (std::is_default_constructible <F>::value, "");
> +struct G { G (); ~G (); };
> +struct H { H (int); ~H (); int h; protected: H () = default; };
> +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, "");
> +union J { D a; int b; };
> +#if __cpp_trivial_union >= 202502L
> +static_assert (std::is_default_constructible <J>::value, "");
> +#else
> +static_assert (!std::is_default_constructible <J>::value, "");
> +#endif
>
>         Jakub
>
>
But again, take it with a huge pile of salt. I'm no expert; I'm just trying
to learn.

--Eric

Reply via email to