Search code examples
c++gccc++20designated-initializer

Why is designated initialization accepted by gcc with no effect, and generated aggregate is somehow passed as arguments to class constructor


Ok, we have this code:

#include <iostream>
using namespace std;

class A{
    public:
    A(double b, int g) {
        cout << "b = " << b << ", g = " << g << endl;
    }
};

int main()
{
    A a = {.g = 5.0, .j = 10};      // output: b = 5, g = 10 
    A b = {.b = 5.0, .g = 10};      // output: b = 5, g = 10
    A bb = { 4.0, 20 };             // output: b = 4, g = 20
    A c = {.g = 5.0, .b = 10, 9};   // error: could not convert ‘{5.0e+0, 10, 9}’ from ‘<brace-enclosed initializer list>’ to ‘A’
    A d = {.g = 5, .b = 10.0};      // error: narrowing conversion of ‘1.0e+1’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
    A e = {.b = 10.0};              // error: could not convert ‘{1.0e+1}’ from ‘<brace-enclosed initializer list>’ to ‘A’
    return 0;
}

Three first lines of main() compile, other produce error in comment. So, experiments show, that:

  1. .name = designated initializer has no effect. Compiler simply ignores any named field
  2. Only order of values in {} braces matters.
  3. Compiler generates <brace-enclosed initializer list> and looks up for constructor of class A with arguments matching elements in list by type and count, otherwise error produced.

g++ compiler 9.3 is used. Passing -pedantic flag throws warning for each value with designator in {} braces

warning: C++ designated initializers only available with ‘-std=c++2a’ or ‘-std=gnu++2a’

Passing -std=c++2a hides the warning, and gives one more error message for A c = {.g = 5.0, .b = 10, 9};:

error: either all initializer clauses should be designated or none of them should be

clang fails to compile two first lines, even with -std=c++2a, producing for each

error: no matching constructor for initialization of 'A'
note: candidate constructor not viable: cannot convert argument of incomplete type 'void' to 'double' for 1st argument
note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided
note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided

Cppreference about Designated initializers in C++ shows only cases of named initialization of class fields, but not constructor arguments. Aggregate initialization and List initialization examples also don't show such feature with constructors.

Well, as stated in the warnings, this case may be treated as C++20 feature, but looking at C++ 20 features, as well as cppreference.com for proposed features gives only designated initialization for aggregate types, but not through constructor arguments, as in my case.

Questions:

  • why and how does it work?
  • Is it ISO C++20 or GNU C++ feature?
  • Should state 1. be treated as gcc compiler silent bug?

Solution

  • A class that has a user defined constructor is not an aggregate class. If designated initialiser is used, then the class shall be an aggregate. Your program uses designated initialiser on a non-aggregate, therefore it is an ill-formed program. Compilers are allowed to not compile it and are required to diagnose the issue.

    Furthermore, a designated initialiser shall name a member of the aggregate. Your program doesn't name a member in the initialiser and therefore it is ill-formed.

    Is it ISO C++20 or GNU C++ feature?

    Designated initialisers in general are a standard C++20 feature, and are also a GNU feature prior to C++20.

    Initialising non-aggregates with designated initialisers is not a standard C++ feature. Whether it is a bug or a feature, it is implementation specific thing.

    Should state 1. be treated as gcc compiler silent bug?

    Lack of diagnostic message is violation of the standard.

    This seems to have been fixed in trunk and all cases using designated initialisers are an error in GCC: https://godbolt.org/z/TWKob6 The latest released version 10.2 still reproduces the lack of error.