On Thu, Jul 23, 2026 at 12:04:40AM -0400, Jason Merrill wrote:
> > And finally, if field for C++29 is from some base class, find out the
> > corresponding base FIELD_DECL and find out how many consecutive designators
> > belong to the same FIELD_DECL and recurse. For anonymous aggregates
> > the recursion is handled slightly differently, we call reshape_init_r
> > instead of reshape_init_class and reshape_init_class called from it will
> > stop looking on first field which is not found. reshape_init_r in the
> > case of designators from a base reports some undesirable errors and where
> > to stop would mean we'd need to pass for the recursion extra arguments
> > and treat designators for testing in one spot as belonging to the desired
> > class and in another again as belonging to the current (i.e. base) class.
>
> It seems unfortunate to need to do all this extra adjustment instead of
> handling it by the usual reshaping. If the designator names a base member,
> would it work to clear direct_desig (and set field to the base field) so the
> existing code recurses like for an anonymous aggr member?
That is actually what I've implemented first, but it doesn't work, several
tests in the added tests then fail.
The reason why it works for the ANON_AGGR_TYPE_P cases is that the
recursive reshape_init_r -> reshape_init_class has:
if (!field && ANON_AGGR_TYPE_P (type))
/* Apparently the designator isn't for a member of this anonymous
struct, so head back to the enclosing class. */
break;
so, it will stop after all (in case of anon union after the first)
initializer clause.
Now, for the end of the set of initializer clauses which belong to a
particular base class (and so should be handled together by the recursive
call), the condition is much more complicated. If we passed that outer type
to the recursion as a new argument, we'd need to repeat the lookups using
that type, i.e. all of that get_class_binding + lookup_member +
same_type_ignoring_top_level_qualifiers_p + lookup_base etc. And we
would need to do it not just in the if (!field && parent_type) case.
Consider
struct A { int a, x; };
struct B { int b, x; };
struct C : A, B { int c; };
struct D : C { int x; };
auto d = D { .c = 1, .x = 2 };
I believe according to the rules added in the paper, this should be valid
and same as D { { { }, { }, .c = 1 }, .x = 2 }; because x is a member of
the D class, a is not, it belongs to the C base class. Now, if we lookup
x when in the recursion inside of the C base, x is ambiguous, so field
will not be NULL, but TREE_LIST. Even if it wasn't ambiguous there, i.e.
struct E { int a, x; };
struct F : E { int x; };
auto f = F { .a = 1, .x = 2 };
so field inside of E recursion would be E::x FIELD_DECL, we don't want
that, it should be F::x, so F { { .a = 1 }, .x = 2 }.
As we have to repeat all that stuff for each clause (i.e. try to look it
up and check if it belongs to the same base class as the last one and then
look it up again inside of that base class), I thought it would be better
to perform this in the caller, not within recursion which would need extra
arguments.
If I add && 0 into else if (new_end) (which is roughly equivalent to
getting rid of the whole loop trying to compute new_end), then I get
FAIL: g++.dg/cpp29/desig1.C -std=c++29 (internal compiler error: in
reshape_init_r, at cp/decl.cc:8316)
FAIL: g++.dg/cpp29/desig1.C -std=c++29 (test for errors, line 11)
FAIL: g++.dg/cpp29/desig1.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig10.C -std=c++29 (test for errors, line 27)
FAIL: g++.dg/cpp29/desig10.C -std=c++29 (test for errors, line 28)
FAIL: g++.dg/cpp29/desig10.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig3.C -std=c++29 (internal compiler error: in
reshape_init_r, at cp/decl.cc:8316)
FAIL: g++.dg/cpp29/desig3.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig4.C -std=c++29 (test for errors, line 11)
FAIL: g++.dg/cpp29/desig4.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig5.C -std=c++29 (test for errors, line 13)
FAIL: g++.dg/cpp29/desig5.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig6.C -std=c++29 (test for errors, line 14)
FAIL: g++.dg/cpp29/desig6.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig7.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig8.C -std=c++29 (test for errors, line 19)
FAIL: g++.dg/cpp29/desig8.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp2a/desig20.C -std=c++29 (test for excess errors)
There is another thing I wrote about above, in particular that
reshape_init_r for the recursion doesn't work. So, if I instead of
the && 0 change in the else if (new_end) block the reshape_init_class
call to reshape_init_r (again, I had that in the WIP versions of the
patch before like that), then it is just
FAIL: g++.dg/cpp29/desig1.C -std=c++29 (internal compiler error: in
reshape_init_r, at cp/decl.cc:8316)
FAIL: g++.dg/cpp29/desig1.C -std=c++29 (test for errors, line 11)
FAIL: g++.dg/cpp29/desig1.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig3.C -std=c++29 (internal compiler error: in
reshape_init_r, at cp/decl.cc:8316)
FAIL: g++.dg/cpp29/desig3.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig5.C -std=c++29 (test for errors, line 13)
FAIL: g++.dg/cpp29/desig5.C -std=c++29 (test for excess errors)
FAIL: g++.dg/cpp29/desig8.C -std=c++29 (test for errors, line 19)
that fails.
Now, I guess there is another possibility how to deal with this in the
caller. In my patch it is the old lookup with some small additions
and if it detects it belongs to a base, it finds new_end (and in that
loop unfortunately repeats quite a few calls from the normal path).
Instead, we could just clear direct_desig and set 2 variables when
we find it belongs to a base, constructor_elt *base_d to point to the
first one that needs to be recursed on and the base's aafield.
And in that case don't recurse, defer the reshape_init_class recursive
call till later. And then continue the loop.
The complication is that we need to invoke this deferred
reshape_init_class in several spots. I've left out all the
return error_mark_node; cases, which means that perhaps some errors
might not be reported (if there is an error later in the initializer
after something appertaining to a base and in the base too, if the
recursion is skipped due to later error, the errors in the base (e.g.
ambiguous lookup) won't be reported.
gcc/cp/decl.cc diff for this variant (passes
GXX_TESTSUITE_STDS=98,11,14,17,20,23,26,29 make check-g++
RUNTESTFLAGS="dg.exp='desig*.C feat-cxx29.C embed-14.C pr43765.C'"
) attached (though, it is more lines of code compared to the previously
posted one).
> > @@ -29406,13 +29406,24 @@ cp_parser_initializer_list (cp_parser* p
> > }
> > else if (cxx_dialect >= cxx20
> > && first_designator != error_mark_node
> > - && (!first_designator != !designator))
> > + && (!first_designator != !designator)
> > + && (cxx_dialect < cxx29
> > + || first_designator
> > + || TREE_CODE (designator) != IDENTIFIER_NODE))
> > {
> > - error_at (loc, "either all initializer clauses should be designated "
> > - "or none of them should be");
> > + if (cxx_dialect < cxx29
> > + || TREE_CODE (first_designator
> > + ? first_designator
> > + : designator) != IDENTIFIER_NODE)
> > + error_at (loc, "either all initializer clauses should be "
> > + "designated or none of them should be");
>
> Can we make it a -Wc++29-extensions pedwarn in earlier modes?
So do you want this to be a partially supported extension in C++ 20 to 26
with pedwarns? What would that do?
The paper has basically two parts, for
struct A { int a; };
struct B : A { int b; };
one is to allow
B { { .a = 1 }, .b = 2 }
and the other is to allow
B { .a = 1, .b = 2 }
Allowing the latter in C++20 to C++26 is a bad idea IMHO, because that
changes the behavior of some valid code, as e.g.
https://eel.is/c++draft/diff.cpp26.dcl#1
shows.
Supporting the former as an extension with pedwarns is possible, but it
isn't about changing one error_at to pedwarn IMHO.
In the patch currently, the parser.cc change arranges for the
error_at (loc, "either all initializer clauses should be designated "
"or none of them should be");
error not to be emitted for C++29 unless it is the [0] = designator case,
and emit the other error
error_at (loc, "designated initializer clause should not "
"be followed by non-designated");
for C++29 if it is non-designated after designated (and nothing for
designated after non-designated).
What should we do for C++20 to C++26? Just emit the
error_at (loc, "either all initializer clauses should be designated "
"or none of them should be");
error in the non-designated after designated and emit the same thing
as pedwarn for designated after non-designated? I'm afraid anything else
would just confuse users.
And the
+ if (complain & tf_error)
+ error ("last non-designated initializer clause "
+ "does not appertain to a base class "
+ "subobject");
error in decl.cc would need to be done for cxx_dialect >= cxx20
rather than cxx_dialect >= cxx29.
Jakub
--- gcc/cp/decl.cc.jj 2026-07-23 09:19:31.856296155 +0200
+++ gcc/cp/decl.cc 2026-07-23 10:24:36.528705474 +0200
@@ -7831,6 +7831,34 @@ reshape_init_class (tree type, reshape_i
/* For C++20 CTAD, handle pack expansions in the base list. */
tree last_was_pack_expansion = NULL_TREE;
+ bool first_desig = true;
+ constructor_elt *base_cur = NULL;
+ tree base_ctx = NULL_TREE, base_field = NULL_TREE;
+ auto reshape_init_base_class = [] (constructor_elt *base_cur,
+ reshape_iter *d, tree base_field,
+ tree new_init, tsubst_flags_t complain)
+ {
+ reshape_iter rd;
+ rd.cur = base_cur;
+ rd.end = d->cur;
+ rd.raw_idx = d->raw_idx;
+ tree field_init = reshape_init_class (TREE_TYPE (base_field), &rd,
+ /*first_initializer_p=*/NULL_TREE,
+ complain);
+ if (field_init == error_mark_node)
+ return error_mark_node;
+
+ if (rd.cur != rd.end || rd.raw_idx != d->raw_idx)
+ {
+ if (complain & tf_error)
+ error ("invalid initializer for %q#D", base_field);
+ return error_mark_node;
+ }
+
+ CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (new_init), base_field,
+ field_init);
+ return NULL_TREE;
+ };
/* Loop through the initializable fields, gathering initializers. */
while (d->cur != d->end)
@@ -7839,6 +7867,7 @@ reshape_init_class (tree type, reshape_i
constructor_elt *old_cur = d->cur;
unsigned old_raw_idx = d->raw_idx;
bool direct_desig = false;
+ tree new_base_ctx = NULL_TREE;
/* Handle C++20 designated initializers. */
if (d->cur->index)
@@ -7861,9 +7890,31 @@ reshape_init_class (tree type, reshape_i
}
else if (TREE_CODE (d->cur->index) == IDENTIFIER_NODE)
{
+ if (first_desig && cxx_dialect >= cxx29)
+ {
+ if (CONSTRUCTOR_NELTS (new_init))
+ {
+ constructor_elt *last
+ = &CONSTRUCTOR_ELTS (new_init)->last ();
+ if (last->index == NULL_TREE
+ || TREE_CODE (last->index) != FIELD_DECL
+ || !DECL_FIELD_IS_BASE (last->index))
+ {
+ if (complain & tf_error)
+ error ("last non-designated initializer clause "
+ "does not appertain to a base class "
+ "subobject");
+ return error_mark_node;
+ }
+ }
+ first_desig = false;
+ }
CONSTRUCTOR_IS_DESIGNATED_INIT (new_init) = true;
field = get_class_binding (type, d->cur->index);
direct_desig = true;
+ if (!field && cxx_dialect >= cxx29)
+ field = lookup_member (type, d->cur->index, /*protect=*/2,
+ /*want_type=*/false, complain);
}
else
{
@@ -7913,6 +7964,20 @@ reshape_init_class (tree type, reshape_i
ictx = cctx;
}
+ if (cxx_dialect >= cxx29)
+ {
+ tree ibinfo = lookup_base (type, ictx, ba_unique, NULL,
+ complain);
+ if (ibinfo && ibinfo != error_mark_node)
+ {
+ while (BINFO_INHERITANCE_CHAIN (ibinfo) != binfo)
+ ibinfo = BINFO_INHERITANCE_CHAIN (ibinfo);
+ ictx = TREE_TYPE (ibinfo);
+ new_base_ctx = ictx;
+ goto found;
+ }
+ }
+
/* Not found, e.g. FIELD is a member of a base class. */
if (complain & tf_error)
error ("%qD is not a direct member of %qT", field, type);
@@ -7930,6 +7995,27 @@ reshape_init_class (tree type, reshape_i
}
}
+ /* Handle deferred recursion for bases if needed. */
+ if (base_cur && new_base_ctx != base_ctx)
+ {
+ field_init = reshape_init_base_class (base_cur, d, base_field,
+ new_init, complain);
+ if (field_init == error_mark_node)
+ return error_mark_node;
+ base_cur = NULL;
+ }
+ if (base_cur == NULL && new_base_ctx)
+ {
+ base_cur = d->cur;
+ base_ctx = new_base_ctx;
+ base_field = field;
+ }
+ if (base_cur)
+ {
+ d->cur++;
+ continue;
+ }
+
/* If we processed all the member of the class, we are done. */
if (!field)
break;
@@ -7997,6 +8083,11 @@ reshape_init_class (tree type, reshape_i
field = next_aggregate_field (DECL_CHAIN (field));
}
+ if (base_cur
+ && reshape_init_base_class (base_cur, d, base_field,
+ new_init, complain) == error_mark_node)
+ return error_mark_node;
+
/* A trailing aggregate element that is a pack expansion is assumed to
correspond to all remaining elements of the initializer list (if any). */
if (last_was_pack_expansion)