Инициализация значения: MSVC vs clang

#include<cstddef>

template<typename T, std::size_t N>
struct A {
    T m_a[N];
    A() : m_a{} {}
};

struct S {
    explicit S(int i=4) {}
};

int main() {
    A<S, 3> an;
}

Вышеупомянутый код отлично компилируется с помощью MSVC (2017), но не работает с clang 3.8.0 (вывод clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp):

clang version 3.8.0 (tags/RELEASE_380/final 263969)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin
main.cpp:6:15: error: chosen constructor is explicit in copy-initialization
    A() : m_a{} {}
              ^
main.cpp:14:13: note: in instantiation of member function 'A<S, 3>::A' requested here
    A<S, 3> an;
            ^
main.cpp:10:14: note: constructor declared here
    explicit S(int i=4) {}
             ^
main.cpp:6:15: note: in implicit initialization of array element 0 with omitted initializer
    A() : m_a{} {}
              ^
1 error generated.

clang 5.0 также отказывается компилировать это:

<source>:6:17: error: expected member name or ';' after declaration specifiers
    A() : m_a{} {}
                ^
<source>:6:14: error: expected '('
    A() : m_a{} {}
             ^
2 errors generated.

Если я использую простые круглые скобки в конструкторе A (т.е. A(): m_a() {}), он компилируется отлично. Из cppreference я бы предположил, что оба они должны привести к тому же (т.е. инициализации значения). Я что-то упустил или это ошибка в одном из компиляторов?

Ответы

Ответ 1

Для m_a{}:

  • [dcl.init]/17.1 отправляет нас в [dcl.init.list], а [dcl.init.list]/3.4 говорит, что мы выполняем агрегатную инициализацию на m_a per [dcl.init.aggr].

    Семантика инициализаторов следующая. [...]

    • Если инициализатор представляет собой (не заключенный в скобки) бит-init-list или is = braced-init-list, объект или ссылка инициализируются по списку.
    • [...]

    Инициализация списка объекта или ссылки типа T определяется следующим образом:

    • [...]
    • В противном случае, если T является агрегатом, выполняется агрегатная инициализация.
    • [...]
  • [dcl.init.aggr]/5.2 говорит, что мы копируем-инициализируем каждый элемент m_a из пустого списка инициализаторов, т.е. {}.

    Для неединичной совокупности каждый элемент, который не является явно инициализированным элементом, инициализируется следующим образом:

    • [...]
    • В противном случае, если элемент не является ссылкой, элемент инициализируется копией из пустого списка инициализаторов ([dcl.init.list]).
    • [...]
  • Это возвращает нас к [dcl.init]/17.1 для инициализации каждого элемента, который снова отправляет нас в [dcl.init.list].
  • На этот раз мы нажмем [dcl.init.list]/3.5, в котором говорится, что элемент инициализируется значением.

    Инициализация списка объекта или ссылки типа T определяется следующим образом:

    • [...]
    • В противном случае, если в списке инициализаторов нет элементов, а T - тип класса с конструктором по умолчанию, объект инициализируется значением.
    • [...]
  • Это приводит нас к [dcl.init]/8.1, в котором говорится, что элемент инициализируется по умолчанию.

    Для инициализации объекта типа T означает:

    • если T является (возможно, cv-квалифицированным) типом класса без конструктора по умолчанию ([class.ctor]) или конструктора по умолчанию, который предоставляется или удаляется пользователем, тогда объект инициализируется по умолчанию;
    • [...]
  • Какие хиты [dcl.init]/7.1, в которых говорится, что мы перечисляем конструкторы на [над.match.ctor] и выполняем разрешение перегрузки на initializer ();

    По умолчанию инициализировать объект типа T:

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

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

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

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

Для m_a():

  • Мы нажмем [dcl.init]/17.4, в котором говорится, что массив инициализируется значением.

    Семантика инициализаторов следующая. [...]

    • [...]
    • Если инициализатор is (), объект инициализируется значением.
    • [...]
  • Это приводит нас к [dcl.init]/8.3, в котором говорится, что каждый элемент инициализируется значением.

    Для инициализации объекта типа T означает:

    • [...]
    • если T - тип массива, то каждый элемент инициализируется значением;
    • [...]
  • Который снова приводит нас к [dcl.init]/8.1, а затем к [dcl.init]/7.1, и поэтому мы снова перечисляем конструкторы на [over.match.ctor] и выполняем разрешение перегрузки на initializer ();

  • На этот раз инициализация по умолчанию не в контексте инициализации копирования, поэтому функции-кандидаты являются "всеми конструкторами класса инициализируемого объекта".
  • На этот раз явный конструктор по умолчанию является кандидатом и выбирается с помощью разрешения перегрузки. Таким образом, программа хорошо сформирована.

Ответ 2

Кланг верен.

Ваша путаница возникает из:

Из cppreference я бы предположил, что оба они должны привести к тому же (т.е. инициализации значения).

Нет, они имеют разные эффекты. Обратите внимание на заметки на этой странице:

Во всех случаях, если используется пустая пара фигурных скобок {}, а T - совокупный тип, вместо инициализации значения выполняется инициализация агрегата.

Это означает, что при инициализации с помощью списка бит-init-list, для агрегатного типа предпочтительной является инициализация агрегата. С A(): m_a{} {}, а m_a - массив, который принадлежит типу aggregate, то вместо этого выполняется инициализация агрегата:

(акцент мой)

Каждая direct public base, (since C++17) элемент direct public base, (since C++17) или нестатический член класса в порядке индекса/внешнего вида массива в определении класса, инициализируется с копией из соответствующего предложения списка инициализаторов.

а также

Если количество предложений инициализатора меньше числа членов and bases (since C++17) или список инициализаторов полностью пуст, остальные члены and bases (since C++17) инициализируются инициализаторами by their default initializers, if provided in the class definition, and otherwise (since C++14) пустым списком в соответствии с обычными правилами инициализации списка (который выполняет инициализацию инициализации значения для неклассических типов и неагрегатных классов со стандартными конструкторами и агрегатной инициализацией для агрегатов).

Это означает, что остальные элементы, т. m_a Все три элемента m_a будут инициализированы с копией из пустого списка; для пустого списка будет рассмотрен конструктор по умолчанию S, но он будет объявлен как explicit; инициализация копирования не будет вызывать explicit конструкторы:

copy-list-initialization (рассматриваются как явные, так и неявные конструкторы, но могут быть вызваны только неявные конструкторы)


С другой стороны, A(): m_a() {} выполняет инициализацию значения, тогда

3) если T - тип массива, каждый элемент массива инициализируется значением;

затем

1) если T - тип класса без конструктора по умолчанию или с предоставленным пользователем или удаленным конструктором по умолчанию, объект инициализируется по умолчанию;

то для инициализации элементов m_a вызывается конструктор по умолчанию S Является ли это explicit или нет, не имеет значения для инициализации по умолчанию.

Ответ 3

Это явно плохо сформировано Стандартом (вопрос, однако, почему?):

m_a{} list-инициализирует S::m_a:

[dcl.init.list]/1

Инициализация списка - это инициализация объекта или ссылки из списка с привязкой к init. Такой инициализатор называется списком инициализатора, а разделяемые запятой инициализаторы-предложения инициализатора-списка или назначенные-инициализаторы-предложения списка назначенных-инициализаторов называются элементами списка инициализаторов. Список инициализаторов может быть пустым. Инициализация списка может возникать в контекстах с прямой инициализацией или копированием; инициализация списка в контексте прямой инициализации называется инициализацией прямого списка, а инициализация списка в контексте инициализации копирования называется copy-list-initialization.

В качестве массива A<S, 3>::m_a является агрегированным типом ( [dcl.init.aggr]/1).

[dcl.init.aggr]/3.3

  1. Когда агрегат инициализируется списком инициализаторов, как указано в [dcl.init.list], [...]
    3.3 список инициализаторов должен быть {}, и нет явно инициализированных элементов.

так как нет явно инициализированных элементов:

[dcl.init.aggr]/5.2

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

Каждый S из A<S, 3>::m_a затем инициализируется копированием:

[dcl.init]/17.6.3

  1. Семантика инициализаторов следующая. Тип назначения - тип инициализированного объекта или ссылки, а тип источника - тип выражения инициализатора. Если инициализатор не является одним (возможно, в скобках) выражением, тип источника не определен. [...]
    17.6 Если тип адресата является классом класса (возможно, с квалификацией cv): [...]
    17.6.3 В противном случае (т.е. для остальных случаев инициализации копирования) пользовательские последовательности преобразования, которые могут преобразовываться из типа источника в тип назначения или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в [over.match.copy], а лучший выбирается с помощью разрешения перегрузки. Если преобразование не может быть выполнено или неоднозначно, инициализация плохо сформирована.

Поскольку конструктор по умолчанию S является явным, он не может преобразовать из типа источника в тип назначения (S).

С другой стороны, синтаксис с использованием m_a() является агрегатной инициализацией члена и не вызывает инициализацию копирования.

Ответ 4

Если я правильно понимаю стандарт, то это правильно.

Согласно [dcl.init.aggr]/8.5.1-2

Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся как инициализаторы для членов агрегата, увеличивая индекс или порядок членов. Каждый член инициализируется копией из соответствующего предложения инициализатора.

И далее в том же предложении [dcl.init.aggr]/8.5.1:7

Если в списке меньше предложений-инициализаторов, чем в совокупности, то каждый член, который явно не инициализирован, должен быть инициализирован из своего элемента с равным-равным-инициализатором или, если нет инициализатора с фигурной скобкой или равным, из пустого списка инициализаторов

В соответствии с правилами инициализации списка [over.match.list]/13.3.1.7

В инициализации списка копий, если выбран явный конструктор, инициализация плохо сформирована.