Явный конструктор по умолчанию

Этот код отлично компилируется с помощью GCC 5.X, MSVC, но GCC 6.X дает ошибку:

"преобразование в" a "из списка инициализаторов будет использовать явный конструктор" a :: a() "," выбранный конструктор "clang" явно выражен в копировании-инициализации ".

Удаление explicit или переход на ac{} устраняет проблему, но мне любопытно, почему она работает именно так.

class a
{
public:
    explicit a () {}
};
struct b
{
    a c;
};

int main() {
    b d{};
}

Ответ 1

b - совокупность. Когда вы инициализируете его с помощью списка инициализаторов, элементы в списке инициализируют первые n членов агрегата, где n - количество элементов в списке. Остальные элементы агрегата инициализируются списком-списком.

Таким образом, в вашем примере c будет инициализироваться с помощью списка копий, но это плохо сформировано, если выбранный конструктор explicit, следовательно, ошибка.

Соответствующие стандартные котировки

[dcl.init.aggr]/3

Когда агрегат инициализируется списком инициализаторов, как указано в [dcl.init.list], элементы списка инициализаторов берутся как инициализаторы для элементов агрегата. Явно инициализированные элементы совокупности определяются следующим образом:
...
- Если список инициализаторов является списком инициализаторов, явно инициализированные элементы агрегата являются первыми n элементами агрегата, где n - количество элементов в списке инициализаторов.
- В противном случае список инициализаторов должен быть {}, и нет явно инициализированных элементов.

[dcl.init.aggr]/5

Для неединичной совокупности каждый элемент, который не является явно инициализированным элементом, инициализируется следующим образом:
...
- В противном случае, если элемент не является ссылкой, элемент инициализируется копией из пустого списка инициализаторов ([dcl.init.list]).

Эффект инициализации копирования c из пустого списка инициализаторов описан в

[dcl.init.list]/3

Инициализация списка объекта или ссылки типа T определяется следующим образом:
...
- В противном случае, если в списке инициализаторов нет элементов, а T - тип класса с конструктором по умолчанию, объект инициализируется значением.

[dcl.init]/8

Для инициализации объекта типа T означает:
...
- если T - тип класса (возможно, cv-qualit) без конструктора по умолчанию ([class.ctor]) или конструктора по умолчанию, который предоставляется или удаляется пользователем, тогда объект инициализируется по умолчанию;

[dcl.init]/7

По умолчанию инициализировать объект типа T:
- Если T является классом класса (возможно, cv-qualit), рассматриваются конструкторы. Соответствующие конструкторы перечисляются ([over.match.ctor]), а лучший для инициализатора() выбирается посредством разрешения перегрузки. Выбранный таким образом конструктор вызывается с пустым списком аргументов для инициализации объекта.

[over.match.ctor]

... Для инициализации копии, функции-кандидаты являются всеми конструкторами преобразования этого класса.

[class.conv.ctor]/1

Конструктор, объявленный без explicit спецификатора функции, указывает преобразование из типов его параметров (если есть) в тип своего класса. Такой конструктор называется конструктором преобразования.

В приведенном выше примере a не имеет преобразовывающих конструкторов, поэтому разрешение перегрузки выходит из строя. Пример (ненормативный) в [class.conv.ctor]/2 даже содержит очень похожий случай

  struct Z {
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
  };

  Z c = {};                       // error: copy-list-initialization

Вы можете избежать ошибки, предоставив инициализатор элемента по умолчанию для c

struct b
{
    a c{};  // direct-list-initialization, explicit ctor is OK
};