Инициализация значения: 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
:
Инициализация списка - это инициализация объекта или ссылки из списка с привязкой к init. Такой инициализатор называется списком инициализатора, а разделяемые запятой инициализаторы-предложения инициализатора-списка или назначенные-инициализаторы-предложения списка назначенных-инициализаторов называются элементами списка инициализаторов. Список инициализаторов может быть пустым. Инициализация списка может возникать в контекстах с прямой инициализацией или копированием; инициализация списка в контексте прямой инициализации называется инициализацией прямого списка, а инициализация списка в контексте инициализации копирования называется copy-list-initialization.
В качестве массива A<S, 3>::m_a
является агрегированным типом ( [dcl.init.aggr]/1
).
- Когда агрегат инициализируется списком инициализаторов, как указано в [dcl.init.list], [...]
3.3 список инициализаторов должен быть {}
, и нет явно инициализированных элементов.
так как нет явно инициализированных элементов:
- Для неединичной совокупности каждый элемент, который не является явно инициализированным, инициализируется следующим образом: [...]
5.2, если элемент не является ссылкой, элемент инициализируется копией из пустого списка инициализаторов ([dcl.init.list]
).
Каждый S
из A<S, 3>::m_a
затем инициализируется копированием:
- Семантика инициализаторов следующая. Тип назначения - тип инициализированного объекта или ссылки, а тип источника - тип выражения инициализатора. Если инициализатор не является одним (возможно, в скобках) выражением, тип источника не определен. [...]
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
В инициализации списка копий, если выбран явный конструктор, инициализация плохо сформирована.