List-initialization (since C++11)
Инициализирует объект из списка инициализации в фигурных скобках .
Содержание |
Синтаксис
Прямая инициализация списком
T объект
{
арг1, арг2, ...
};
|
(1) | ||||||||
T
{
арг1, арг2, ...
}
|
(2) | ||||||||
new
T
{
арг1, арг2, ...
}
|
(3) | ||||||||
Класс
{
T член
{
арг1, арг2, ...
}; };
|
(4) | ||||||||
Класс
::
Класс
() :
член
{
арг1, арг2, ...
} {...
|
(5) | ||||||||
Копирующая инициализация списком
T объект
= {
арг1, арг2, ...
};
|
(6) | ||||||||
функция
({
арг1, арг2, ...
})
|
(7) | ||||||||
return {
арг1, арг2, ...
};
|
(8) | ||||||||
объект
[{
арг1, арг2, ...
}]
|
(9) | ||||||||
объект
= {
арг1, арг2, ...
}
|
(10) | ||||||||
U
({
арг1, арг2, ...
})
|
(11) | ||||||||
Класс
{
T член
= {
арг1, арг2, ...
}; };
|
(12) | ||||||||
Инициализация списком выполняется в следующих ситуациях:
- прямая инициализация списком (рассматриваются как явные, так и неявные конструкторы)
- copy-list-initialization (рассматриваются как explicit, так и non-explicit конструкторы, но вызываться могут только non-explicit конструкторы)
operator[]
, где list-initialization инициализирует параметр перегруженного оператора
U
в этом примере не является типом, который инициализируется списком;
U
's параметр конструктора является)
Объяснение
Эффекты от list-инициализации объекта типа (возможно, cv-квалифицированного)
T
следующие:
|
(начиная с C++20) |
-
Если
Tявляется агрегатным классом и список инициализации в фигурных скобках , не содержащий designated initializer list, (начиная с C++20) имеет единственный элемент инициализации того же или производного типа (возможно, с cv-квалификаторами), объект инициализируется из этого элемента (через copy-initialization для copy-list-initialization, или через direct-initialization для direct-list-initialization). -
Иначе, если
Tявляется массивом символов и список инициализации в фигурных скобках имеет единственный элемент инициализации, который является строковым литералом подходящего типа, массив инициализируется из строкового литерала как обычно .
-
В противном случае, если
Tявляется агрегатным типом , агрегатная инициализация выполняется.
-
В противном случае, если список инициализации в фигурных скобках пуст и
Tявляется классом с конструктором по умолчанию, value-initialization выполняется.
-
В противном случае, если
Tявляется специализацией std::initializer_list , объект инициализируется, как описано ниже .
-
В противном случае, если
Tявляется типом класса, конструкторыTрассматриваются в две фазы:
-
- Все конструкторы, принимающие std::initializer_list в качестве единственного аргумента, или в качестве первого аргумента, если остальные аргументы имеют значения по умолчанию, рассматриваются и сопоставляются посредством overload resolution с единственным аргументом типа std::initializer_list .
-
-
Если предыдущая стадия не приводит к совпадению, все конструкторы
Tучаствуют в разрешении перегрузки против набора аргументов, состоящего из инициализирующих выражений списка инициализации в фигурных скобках, с ограничением, что допускаются только непрeужающие преобразования. Если на этой стадии в качестве наилучшего соответствия для copy-list-инициализации выбран явный конструктор, компиляция завершается неудачей (обратите внимание, при простой copy-инициализации явные конструкторы вообще не рассматриваются).
-
Если предыдущая стадия не приводит к совпадению, все конструкторы
|
(начиная с C++17) |
-
В противном случае (если
Tне является типом класса), если список инициализации в фигурных скобках содержит только один элемент инициализации и либоTне является ссылочным типом, либо является ссылочным типом, на который ссылается тип, совпадающий с типом элемента инициализации или являющийся его базовым классом,Tподвергается прямой инициализации (при прямой инициализации списком) или копирующей инициализации (при копирующей инициализации списком), за исключением того, что сужающие преобразования не допускаются.
-
В противном случае, если
Tявляется ссылочным типом, не совместимым с типом инициализирующего выражения:
|
(до C++17) |
|
(начиная с C++17) |
-
В противном случае, если список инициализации в фигурных скобках не содержит элементов инициализации,
Tподвергается value-инициализации .
Списковая инициализация std::initializer_list
Объект типа std:: initializer_list < E > конструируется из списка инициализации так, как если бы компилятор сгенерировал и материализовал (начиная с C++17) prvalue типа "массив из N const E ", где N - количество элементов инициализации в списке инициализации; это называется базовым массивом списка инициализации.
Каждый элемент базового массива копирующе инициализируется соответствующим предложением инициализатора из списка инициализации, а объект std:: initializer_list < E > конструируется для ссылки на этот массив. Конструктор или функция преобразования, выбранные для копирования, должны быть доступны в контексте списка инициализации. Если для инициализации любого из элементов требуется сужающее преобразование, программа является некорректной.
Вспомогательный массив имеет то же время жизни, что и любой другой временный объект , за исключением того, что инициализация объекта std::initializer_list из вспомогательного массива продлевает время жизни массива точно так же, как привязка ссылки к временному объекту .
void f(std::initializer_list<double> il); void g(float x) { f({1, x, 3}); } void h() { f({1, 2, 3}); } struct A { mutable int i; }; void q(std::initializer_list<A>); void r() { q({A{1}, A{2}, A{3}}); } // Инициализация выше будет реализована примерно эквивалентно приведённому ниже, // предполагая, что компилятор может сконструировать объект initializer_list с парой // указателей, и с пониманием того, что `__b` не переживает вызов `f`. void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; // backing array f(std::initializer_list<double>(__a, __a + 3)); } void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // backing array f(std::initializer_list<double>(__b, __b + 3)); } void r() { const A __c[3] = {A{1}, A{2}, A{3}}; // backing array q(std::initializer_list<A>(__c, __c + 3)); }
Являются ли все резервные массивы различными (то есть хранятся в непересекающихся объектах ) — не определено:
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2) { return il2.begin() == il1.begin() + 1; } bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // результат не определён: // внутренние массивы могут использовать // общую память в {1, 2, 3, 4}
Сужение преобразований
Списковая инициализация ограничивает допустимые неявные преобразования , запрещая следующие:
- преобразование из типа с плавающей запятой в целочисленный тип
-
преобразование из типа с плавающей запятой
Tв другой тип с плавающей запятой, чей ранг преобразования с плавающей запятой не является ни большим, ни равным рангуT, за исключением случаев, когда результат преобразования является константным выражением и выполняется одно из следующих условий:- Преобразованное значение конечно, и преобразование не вызывает переполнения.
- Значения до и после преобразования не являются конечными.
- преобразование из целочисленного типа в тип с плавающей точкой, за исключением случаев, когда исходное значение является константным выражением, чьё значение может быть точно сохранено в целевом типе
-
преобразование из целочисленного типа или типа неограниченного перечисления в целочисленный тип, который не может представить все значения исходного типа, за исключением случаев, когда
- исходное значение является битовым полем , ширина которого w меньше ширины его типа (или, для типа перечисления , его базового типа), и целевой тип может представить все значения гипотетического расширенного целочисленного типа с шириной w и с той же знаковостью, что и исходный тип, или
- исходное значение является константным выражением, значение которого может быть точно сохранено в целевом типе
- преобразование из типа указателя или типа указателя на член в bool
Примечания
Каждый инициализатор sequenced before любого инициализатора, следующего за ним в списке инициализации в фигурных скобках. Это отличается от аргументов function call expression , которые являются unsequenced (until C++17) indeterminately sequenced (since C++17) .
Список инициализации в фигурных скобках не является выражением и поэтому не имеет типа, например, decltype ( { 1 , 2 } ) является некорректным. Отсутствие типа означает, что вывод типа шаблона не может определить тип, соответствующий списку инициализации в фигурных скобках, поэтому для объявления template < class T > void f ( T ) ; выражение f ( { 1 , 2 , 3 } ) является некорректным. Однако параметр шаблона может быть выведен в других случаях, как в примере с std:: vector < int > v ( std:: istream_iterator < int > ( std:: cin ) , { } ) , где тип итератора выводится из первого аргумента, но также используется во второй позиции параметра. Особое исключение сделано для вывода типа с использованием ключевого слова auto , которое выводит любой список инициализации в фигурных скобках как std::initializer_list при copy-list-инициализации.
Также, поскольку список инициализации в фигурных скобках не имеет типа, специальные правила для разрешения перегрузки применяются, когда он используется в качестве аргумента для вызова перегруженной функции.
Агрегаты копируют/перемещают инициализацию напрямую из списка инициализации в фигурных скобках с единственным инициализирующим выражением того же типа, но неагрегаты сначала рассматривают конструкторы std::initializer_list :
struct X {}; // агрегат struct Q // не-агрегат { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // конструктор копирования (не агрегатная инициализация) Q q; Q q2 = Q{q}; // конструктор со списком инициализации (не конструктор копирования) }
Некоторые компиляторы (например, gcc 10) считают сужающим преобразование из указателя или указателя на член только в bool в режиме C++20.
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_initializer_lists
|
200806L
|
(C++11) | Списковая инициализация и std::initializer_list |
Пример
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // список-инициализация нестатического члена std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // список-инициализация члена в конструкторе }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // список-инициализация в операторе return } int main() { int n0{}; // value-инициализация (до нуля) int n1{1}; // прямая список-инициализация std::string s1{'a', 'b', 'c', 'd'}; // вызов конструктора со списком инициализации std::string s2{s1, 2, 2}; // вызов обычного конструктора std::string s3{0x61, 'a'}; // конструктор со списком инициализации предпочтительнее (int, char) int n2 = {1}; // копирующая список-инициализация double d = double{1.2}; // список-инициализация prvalue, затем копирующая инициализация auto s4 = std::string{"HelloWorld"}; // то же самое, временный объект не создается // начиная с C++17 std::map<int, std::string> m = // вложенная список-инициализация { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // список-инициализация в вызове функции << '\n'; const int (&ar)[2] = {1, 2}; // связывает lvalue-ссылку с временным массивом int&& r1 = {1}; // связывает rvalue-ссылку с временным int // int& r2 = {2}; // ошибка: нельзя связать rvalue с неконстантной lvalue-ссылкой // int bad{1.0}; // ошибка: сужающее преобразование unsigned char uc1{10}; // корректно // unsigned char uc2{-1}; // ошибка: сужающее преобразование Foo f; std::cout << n0 << ' ' << n1 << ' ' << n2 << '\n' << s1 << ' ' << s2 << ' ' << s3 << '\n'; for (auto p : m) std::cout << p.first << ' ' << p.second << '\n'; for (auto n : f.mem) std::cout << n << ' '; for (auto n : f.mem2) std::cout << n << ' '; std::cout << '\n'; [](...){}(d, ar, r1, uc1); // имеет эффект [[maybe_unused]] }
Вывод:
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
Отчеты о дефектах
Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 1288 | C++11 |
список-инициализация ссылки со списком инициализации в фигурных скобках с
единственным элементом всегда связывала ссылку с временным объектом |
связывать с этим элементом
инициализации, если допустимо |
| CWG 1290 | C++11 | время жизни базового массива было указано некорректно |
указано так же, как для других
временных объектов |
| CWG 1324 | C++11 |
при инициализации из
{}
сначала рассматривалась
инициализация |
сначала рассматривается
агрегатная инициализация |
| CWG 1418 | C++11 | тип базового массива не содержал const | const добавлен |
| CWG 1467 | C++11 |
запрещалась инициализация агрегатов и символьных
массивов однотипными значениями; конструкторы со списком инициализации имели приоритет над конструкторами копирования для списков с одним элементом |
однотипная инициализация
разрешена; списки с одним элементом инициализируют напрямую |
| CWG 1494 | C++11 |
при список-инициализации ссылки с элементом инициализации
несовместимого типа не было указано, создается ли временный объект прямой список-инициализацией или копирующей список-инициализацией |
зависит от типа
инициализации для ссылки |
| CWG 2137 | C++11 |
конструкторы со списком инициализации проигрывали
конструкторам копирования при список-инициализации
X
из
{X}
|
неагрегаты сначала
рассматривают списки инициализации |
| CWG 2252 | C++17 | перечисления могли быть список-инициализированы из нескалярных значений | запрещено |
| CWG 2267 | C++11 |
решение
проблемы CWG 1494
прояснило,
что временные объекты могут быть прямой список-инициализированы |
они копирующая список-инициализируются
при список-инициализации ссылок |
| CWG 2374 | C++17 | прямая список-инициализация перечисления допускала слишком много исходных типов | ограничено |
| CWG 2627 | C++11 |
узкое битовое поле большего целочисленного типа может быть продвинуто до
меньшего целочисленного типа, но это все равно считалось сужающим преобразованием |
это не является
сужающим преобразованием |
| CWG 2713 | C++20 |
ссылки на агрегатные классы не могли
быть инициализированы списками с обозначениями |
разрешено |
| CWG 2830 | C++11 | список-инициализация не игнорировала cv-квалификацию верхнего уровня | игнорирует |
| CWG 2864 | C++11 | преобразования с плавающей точкой с переполнением не считались сужающими | они являются сужающими |
| P1957R2 | C++11 |
преобразование из указателя/указателя на член
в bool не считалось сужающим |
считается сужающим |
| P2752R3 | C++11 | базовые массивы с перекрывающимся временем жизни не могли перекрываться | они могут перекрываться |