12

For a while now, one has been able to use "designated initializer" in GCC:

struct CC{
    double a_;
    double b_;
};

CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.bla = 0., .bli = 0.}; // compile error

However when I add a constructor the labels are ignored.

struct CC{
    double a_;
    double b_;
    CC(double a, double b) : a_{a}, b_{b}{}
};

CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.b_ = 2., .a_ = 1.}; // compiles but labels don't matter only the order, confusing
CC cc{.bla = 2., .bli = 1.}; // compiles but labels don't matter, confusing

In other words the initializer syntax with a constructor make the label behave just as a comment!, which can be very confusing, but above all, it is very odd.

I discovered this accidentally, with gcc 8.1 -std=c++2a.

Is this the expected behavior?

Reference: https://en.cppreference.com/w/cpp/language/aggregate_initialization

EDIT 2022: Now that compilers support -std=c++20 the behavior is correct. All the GCC version that accept -std=c++20 (10.1 and above) also accept the above code or give an error when it should.

https://godbolt.org/z/h3eM3T7jK

alfC
  • 14,261
  • 4
  • 67
  • 118

2 Answers2

7

Labels for designated initializers should not be ignored by compilers. All three of those examples with a constructor should be ill-formed.

You are likely getting this behavior due to a conflict between the C++20 designated initializer feature and GCC's C-style designated initializers which you are implicitly accessing due to GCC just giving them to you. If GCC were properly C++20-compiliant, once you gave the type a constructor, it would cease to be an aggregate and thus designated initializer usage would be ill-formed.

Basically, this is a driver bug caused by non-C++-standard behavior the compiler gives you by default. Odds are good that if you turn off this feature, you would get a proper compiler error for those cases.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 2
    "You are likely getting this behavior due to a conflict between the C++20 designated initializer feature and GCC's C-style designated initializers" -- No, GCC just never implemented support for field names in overload resolution, and the presence of a user-defined constructor means overload resolution comes into play. I had a not-so-fun experience recently figuring out what went wrong in a `struct S { A a; B b; C c; };` / `f({1, .c = 2})` call, where clang would compile this as the author (not me) intended, but GCC would attempt to initialise `b` from `2`. –  Sep 12 '18 at 08:35
5

This is a gcc bug, this still builds even with -pedantic in which we should receive warnings for any extensions

...to obtain all the diagnostics required by the standard, you should also specify -pedantic ...

and gcc claims to support the P0329R4: Designated initializers proposal for C++2a mode according to the C++ Standards Support in GCC page:

Language Feature | Proposal | Available in GCC?
...
Designated initializers | P0329R4 | 8

In order to use Designated initializers the type should be aggregate [dcl.init.list]p3.1:

If the braced-init-list contains a designated-initializer-list, T shall be an aggregate class. The ordered identifiers in the designators of the designated-initializer-list shall form a subsequence of the ordered identifiers in the direct non-static data members of T. Aggregate initialization is performed (11.6.1). [ Example:

struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // error: designator order does not match declaration order
A b{.x = 1, .z = 2}; // OK, b.y initialized to 0

—end example ]

CC is not an aggregate according to [dcl.init.aggr]:

An aggregate is an array or a class (Clause 12) with
- (1.1) — no user-provided, explicit, or inherited constructors (15.1),
....

gcc bug report

If we look at gcc bug report: Incorrect overload resolution when using designated initializers we see in this given example:

Another test case, reduced from Chromium 70.0.3538.9 and accepted by clang:

  struct S { void *a; int b; };
  void f(S);
  void g() { f({.b = 1}); }

This fails with

  bug.cc: In function ‘void g()’:
  bug.cc:3:24: error: could not convert ‘{1}’ from ‘<brace-enclosed initializer list>’ to ‘S’
   void g() { f({.b = 1}); }
                        ^

The error suggests the field names are simply ignored entirely during overload resolution, which also explains the behaviour of the originally reported code.

It seems gcc ignores field names during overload resolution. Which would explain the strange behavior you are seeing and that we obtain correct diagnostics when we remove the constructor.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • The class I have in the real is essentially and aggregate but I added a constructor to have template argument deduction. I was happy to see that template argument deduction worked very well with designated initializer, until I realized they were (for whatever reason) no more than just essentially comments. – alfC Sep 11 '18 at 20:00
  • @alfC Don't add a constructor to enable CTAD, add a deduction guide. That said, deduction guide won't help with designated initializers anyway. – Barry Sep 11 '18 at 20:10
  • @Barry, I had the impression that CTAD won't work without a constructor. I will try again. – alfC Sep 11 '18 at 20:12