c++g++language-lawyerc++20designated-initializer

Constructor interferes with member variable designated initializer?


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


Solution

  • 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.