[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