[PATCH] c++, v3: Implement C++26 P3074R7 and CWG3189 - trivial unions [PR119059]
Jason Merrill
jason@redhat.com
Thu May 21 16:29:55 GMT 2026
On 5/21/26 11:28 AM, Jakub Jelinek wrote:
> On Thu, May 21, 2026 at 10:03:56AM -0400, Jason Merrill wrote:
>>> 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.
>>
>> So previously we accepted the case of all const but one has a DMI, which
>> seems fine to me. This would change it to accept all const with no DMI,
>> which seems like a regression for pre-26.
>>
>> If we have been wrongly rejecting a union with some const and some
>> non-const, perhaps we should reconsider your earlier patch to address that.
>
> So, shall that patch be reworked to ignore the case of at least one DMI
> and allow that (even when maybe strictly not valid in < C++26), or as is?
> Can that be handled separately from this patch or shall I merge it into this
> patch?
No need to rework it to preserve the current accepts-invalid, but let's
add some testing for cases we currently wrongly reject, e.g.
union C {
int i;
const int j;
};
C c;
And looking at that patch again, I'm not sure why we need the
all_const_p parameter, I'd think we can handle it all inside
walk_field_subobs.
I think it makes sense for these changes to be separate patches.
> @@ -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)
>>> + if (cxx_dialect >= cxx26 && TREE_CODE (ctx) == UNION_TYPE)
>>> + {
>>
>> Please add more commentary about the semantics, including that we already
>> handled the case of a DMI in a constructor.
>
> The fact that we continue; for DMI in the constructor case doesn't
> affect what this block does, in that case we should ignore all variant
> members, no matter whether they have a DMI or not.
Ah, right, I was thinking the ctor would be deleted if the DMI was
somehow invalid, but instead it's ill-formed.
> While in the sfk_destructor case (both dtor_from_ctor and !dtor_from_ctor
> cases) DMIs are not ignored earlier and so the code checks that explicitly.
>
> Is the following incremental diff ok?
Both incremental diffs look good OK.
> --- gcc/cp/method.cc.jj 2026-05-21 16:52:00.256974840 +0200
> +++ gcc/cp/method.cc 2026-05-21 17:08:36.035025396 +0200
> @@ -2851,9 +2851,45 @@ walk_field_subobs (tree fields, special_
>
> if (cxx_dialect >= cxx26 && TREE_CODE (ctx) == UNION_TYPE)
> {
> + /* C++26 [class.default.ctor]/2:
> + A defaulted default constructor for class X is defined as deleted
> + if
> + ...
> + - any non-variant potentially constructed subobject, except for
> + a non-static data member with a brace-or-equal-initializer, has
> + class type M (or possibly multidimensional array thereof) and
> + overload resolution as applied to find M's corresponding
> + constructor does not result in a usable candidate,
> + So, for C++26 this ignores default constructors of variant
> + members. */
> if (sfk == sfk_constructor || sfk == sfk_inheriting_constructor)
> continue;
>
> + /* C++26 [class.default.ctor]/2:
> + ...
> + - any potentially constructed subobject S has class type M (or
> + possibly multidimensional array thereof), M has a destructor
> + that is deleted or inaccessible from the defaulted default
> + constructor, and either S is non-variant or S has a default
> + member initializer.
> + This is the dtor_from_ctor case, so ignore destructors of
> + variant members unless they have a DMI.
> + C++26 with CWG3189 [class.dtor]/4:
> + A defaulted destructor for a class X is defined as deleted if
> + ...
> + - X is has a non-union class and any non-variant potentially
> + constructed subobject has S of class type M (or possibly
> + multidimensional array thereof) where either
> + - S is not a variant member and M has a destructor that is
> + deleted or is inaccessible from the defaulted destructor, or
> + - S is a variant member, M has a destructor that is deleted,
> + inaccessible from the defaulted destructor, or non-trivial,
> + and either
> + - V S has a default member initializer or
> + - X has a user-provided constructor.
> + This is the !dtor_from_ctor case, so ignore destructors of
> + variant members unless they have a DMI or X has user-provided
> + constructor. */
> if (sfk == sfk_destructor)
> {
> if (!dtor_from_ctor && has_user_provided_ctor == -1)
>
>>> --- 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. */
>>
>> Before C++26, a non-trivial variant member ctor makes t's deleted, so I
>> guess triviality never mattered?
>
> For ctor with the exception of variant member with DMI, but then the default
> ctor is marked non-trivial because of
> if (trivial_p)
> *trivial_p = false;
> in the DECL_INITIAL (field) walk_field_subobs handling.
> So shall I turn this
>>
>>> + if (cxx_dialect < cxx26
>>> + || TREE_CODE (DECL_CONTEXT (field)) != UNION_TYPE)
>
> into
> if (TREE_CODE (DECL_CONTEXT (field)) != UNION_TYPE)
> ?
>
> Jakub
>
More information about the Libstdc++
mailing list