Конструктор вмешивается в инициализатор переменной-члена?

Некоторое время теперь в 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

Однако, когда я добавляю конструктор, метки игнорируются.

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

Другими словами синтаксис инициализатора с конструктором делает ярлык вести себя как комментарий !, что может быть очень запутанным, но, прежде всего, очень странно.

Я обнаружил это случайно, с gcc 8.1 -std=c++2a.

Это ожидаемое поведение?

Ссылка: https://en.cppreference.com/w/cpp/language/aggregate_initialization

Ответ 1

Это ошибка gcc, это все еще строит даже с -pedantic, в котором мы должны получать предупреждения для любых расширений

... чтобы получить все диагностические данные, требуемые стандартом, вы также должны указать -pedantic...

и gcc утверждает, что поддерживает P0329R4: Designated initializers предложение P0329R4: Designated initializers для C++2a соответствии с C++ Поддержка стандартов на странице GCC:

Язык | Предложение | Доступно в GCC?
...
Назначенные инициализаторы | P0329R4 | 8

Чтобы использовать назначенные инициализаторы, тип должен быть агрегирован [dcl.init.list] p3.1:

Если список braced-init содержит список назначенных инициализаторов, T должен быть агрегатным классом. Упорядоченные идентификаторы в указателях списка назначенных-инициализаторов формируют подпоследовательность упорядоченных идентификаторов в прямых нестатических элементах данных T. Выполняется инициализация агрегата (11.6.1). [ Пример:

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 пример]

CC не является агрегатом в соответствии с [dcl.init.aggr]:

Агрегатом является массив или класс (раздел 12) с
- (1.1) - нет пользовательских, явных или унаследованных конструкторов (15.1),
...

Сообщение об ошибке gcc

Если мы посмотрим на отчет gcc bug: неверное разрешение перегрузки при использовании назначенных инициализаторов мы видим в данном примере:

Другой тестовый пример, сокращенный от Chromium 70.0.3538.9 и принятый clang:

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

Это не с

  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}); }
                        ^

Ошибка указывает на то, что имена полей просто игнорируются полностью во время разрешения перегрузки, что также объясняет поведение исходного кода.

Кажется, gcc игнорирует имена полей во время разрешения перегрузки. Что объясняет странное поведение, которое вы видите, и что мы получаем правильную диагностику при удалении конструктора.

Ответ 2

Ярлыки для назначенных инициализаторов не должны игнорироваться компиляторами. Все три из этих примеров с конструктором должны быть плохо сформированы.

Вероятно, вы получаете это поведение из-за конфликта между назначенной инициализацией С++ 20 и инициализаторами, указанными в GCC C, которые вы неявно обращаетесь к GCC, просто предоставляя их вам. Если GCC был надлежащим образом С++ 20-компилятором, как только вы предоставили тип конструктору, он перестанет быть агрегатом, и поэтому использование инициализатора будет неправильным.

В основном, это ошибка драйвера, вызванная не-С++ - стандартным поведением, которое компилятор дает вам по умолчанию. Скорее всего, если вы отключите эту функцию, вы получите правильную ошибку компилятора для этих случаев.