Инициализация списком (начиная с C++11)
Инициализирует объект с помощью списка инициализаторов в фигурных скобках.
Синтаксис
Прямая инициализация списком
T object { arg1, arg2, ... };
| (1) | |||
T { arg1, arg2, ... }
| (2) | |||
new T { arg1, arg2, ... }
| (3) | |||
Class { T member { arg1, arg2, ... }; };
| (4) | |||
Class::Class() : member { arg1, arg2, ... } {...
| (5) |
Копирующая инициализация списком
T object = { arg1, arg2, ... };
| (6) | |||
function ({ arg1, arg2, ... })
| (7) | |||
return { arg1, arg2, ... };
| (8) | |||
object [{ arg1, arg2, ... }]
| (9) | |||
object = { arg1, arg2, ... }
| (10) | |||
U ({ arg1, arg2, ... })
| (11) | |||
Class { T member = { arg1, arg2, ... }; };
| (12) |
Инициализация списком выполняется в следующих случаях:
- прямая инициализация списком (рассматриваются как явные, так и неявные конструкторы)
- копирующая инициализация списком (рассматриваются как явные, так и неявные конструкторы, но вызываться могут только неявные конструкторы)
operator[], где параметр перегруженного оператора инициализируется спискомU в этом примере не является типом, инициализируемым списком; инициализируется параметр конструктора U)Пояснение
Результат инициализации списком объекта типа (возможно, cv-квалифицированного) T:
| (начиная с C++20) |
- Если
Tявляется агрегатным классом и список инициализаторов в фигурных скобках, который не содержит списка designated-инициализаторов,(начиная с C++20) содержит единственную инициализирующую клаузу того же или производного типа (возможно, с квалификаторами cv), объект инициализируется этой инициализирующей клаузой (посредством копирующей инициализации при копирующей инициализации списком или посредством прямой инициализации при прямой инициализации списком). - В противном случае, если
Tявляется массивом символов и список инициализаторов в фигурных скобках содержит единственную инициализирующую клаузу, представляющую собой строковый литерал подходящего типа, массив инициализируется из строкового литерала обычным образом. - В противном случае, если
Tявляется агрегатным типом, выполняется агрегатная инициализация. - В противном случае, если список инициализаторов в фигурных скобках пуст и
Tявляется типом класса с конструктором по умолчанию, выполняется инициализация значением. - В противном случае, если
Tявляется специализациейstd::initializer_list, объект инициализируется, как описано ниже. - В противном случае, если
Tявляется типом класса, конструкторыTрассматриваются в два этапа: - Рассматриваются все конструкторы, принимающие
std::initializer_listв качестве единственного аргумента или в качестве первого аргумента, если для остальных аргументов заданы значения по умолчанию; для выбора подходящего конструктора применяется разрешение перегрузки с одним аргументом типаstd::initializer_list. - Если на предыдущем этапе подходящий вариант не найден, все конструкторы
Tучаствуют в разрешении перегрузки относительно набора аргументов, состоящего из инициализирующих клауз списка инициализаторов в фигурных скобках; при этом разрешены только сужающие преобразования. Если на этом этапе лучшим вариантом для копирующей инициализации списком выбран явный конструктор, компиляция завершается ошибкой (обратите внимание: при простой копирующей инициализации явные конструкторы вообще не рассматриваются).
| (начиная с C++17) |
- В противном случае (если
Tне является типом класса), если список инициализаторов в фигурных скобках содержит только одну инициализирующую клаузу, аTлибо не является ссылочным типом, либо является ссылочным типом, тип объекта по ссылке которого совпадает с типом инициализирующей клаузы или является его базовым классом,Tинициализируется прямой инициализацией (при прямой инициализации списком) или копирующей инициализацией (при копирующей инициализации списком), за исключением того, что сужающие преобразования не допускаются. - В противном случае, если
Tявляется ссылочным типом, несовместимым с типом инициализирующей клаузы:
| (до C++17) |
| (начиная с C++17) |
- В противном случае, если список инициализаторов в фигурных скобках не содержит инициализирующих клауз,
Tинициализируется значением.
Инициализация списком std::initializer_list
Объект типа std::initializer_list<E> создаётся из списка инициализаторов так, как если бы компилятор создал и материализовал(начиная с C++17) prvalue типа «массив из N constE», где 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}});
}
// The initialization above will be implemented in a way roughly equivalent to below,
// assuming that the compiler can construct an initializer_list object with a pair of
// pointers, and with the understanding that `__b` does not outlive the call to `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}); // the result is unspecified:
// the back arrays can share
// storage within {1, 2, 3, 4}
Сужающие преобразования
Инициализация списком ограничивает допустимые неявные преобразования, запрещая следующие:
- преобразование из типа с плавающей точкой в целочисленный тип
- преобразование из типа с плавающей точкой
Tв другой тип с плавающей точкой, ранг преобразования которого не выше и не равен рангуT, за исключением случаев, когда результат преобразования является константным выражением и выполняется одно из следующих условий:- Преобразованное значение является конечным, и преобразование не приводит к переполнению.
- Значения до и после преобразования не являются конечными.
- преобразование из целочисленного типа в тип с плавающей точкой, за исключением случаев, когда исходное значение является константным выражением, которое может быть точно представлено целевым типом
- преобразование из целочисленного типа или типа неперечислимого перечисления в целочисленный тип, который не может представить все значения исходного типа, за исключением случаев, когда
- источник является битовым полем, ширина
wкоторого меньше ширины его типа (или, для типа перечисления, его базового типа), а целевой тип может представить все значения гипотетического расширенного целочисленного типа шириныwс тем же знаком, что и у исходного типа, или - источник является константным выражением, значение которого может быть точно представлено целевым типом
- источник является битовым полем, ширина
- преобразование из типа указателя или типа указателя на член в bool
Примечания
Каждая инициализирующая клауза упорядочена перед любой следующей за ней инициализирующей клаузой списка инициализаторов в фигурных скобках. Это отличается от аргументов выражения вызова функции, которые являются неупорядоченными(до C++17)неопределённо упорядоченными(начиная с 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.
Также из-за отсутствия типа у списка инициализаторов в фигурных скобках при его использовании в качестве аргумента перегруженного вызова функции применяются специальные правила разрешения перегрузки.
Агрегаты напрямую копирующе/перемещающе инициализируются списком инициализаторов в фигурных скобках, состоящим из одной инициализирующей клаузы того же типа, а для неагрегатных типов в первую очередь рассматриваются конструкторы std::initializer_list:
struct X {}; // aggregate
struct Q // non-aggregate
{
Q() = default;
Q(Q const&) = default;
Q(std::initializer_list<Q>) {}
};
int main()
{
X x;
X x2 = X{x}; // copy-constructor (not aggregate initialization)
Q q;
Q q2 = Q{q}; // initializer-list constructor (not copy constructor)
}
Некоторые компиляторы (например, 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}; // list-initialization of a non-static member
std::vector<int> mem2;
Foo() : mem2{-1, -2, -3} {} // list-initialization of a member in constructor
};
std::pair<std::string, std::string> f(std::pair<std::string, std::string> p)
{
return {p.second, p.first}; // list-initialization in return statement
}
int main()
{
int n0{}; // value-initialization (to zero)
int n1{1}; // direct-list-initialization
std::string s1{'a', 'b', 'c', 'd'}; // initializer-list constructor call
std::string s2{s1, 2, 2}; // regular constructor call
std::string s3{0x61, 'a'}; // initializer-list ctor is preferred to (int, char)
int n2 = {1}; // copy-list-initialization
double d = double{1.2}; // list-initialization of a prvalue, then copy-init
auto s4 = std::string{"HelloWorld"}; // same as above, no temporary
// created since C++17
std::map<int, std::string> m = // nested list-initialization
{
{1, "a"},
{2, {'a', 'b', 'c'}},
{3, s1}
};
std::cout << f({"hello", "world"}).first // list-initialization in function call
<< '\n';
const int (&ar)[2] = {1, 2}; // binds an lvalue reference to a temporary array
int&& r1 = {1}; // binds an rvalue reference to a temporary int
// int& r2 = {2}; // error: cannot bind rvalue to a non-const lvalue ref
// int bad{1.0}; // error: narrowing conversion
unsigned char uc1{10}; // okay
// unsigned char uc2{-1}; // error: narrowing conversion
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); // has effect of [[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 | поддерживающие массивы с пересекающимися временами жизни не могли перекрываться | могут перекрываться |
См. также
- конструктор
- преобразующий конструктор
- оператор копирующего присваивания
- конструктор копирования
- элизия копирования
- конструктор по умолчанию
explicit- инициализация
- оператор перемещающего присваивания
- конструктор перемещения
new
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/list_initialization