Инициализация списком (с C++11)
Инициализирует объект из списка-инициализатора.
Синтаксис
Прямая инициализация списком
T object { arg1, arg2, ... };
| (1) | |||
T { arg1, arg2, ... }
| (2) | |||
new T { arg1, arg2, ... }
| (3) | |||
Класс { T member { arg1, arg2, ... }; };
| (4) | |||
Класс::Класс() : member { arg1, arg2, ... } {...
| (5) |
Копирующая инициализация списком
T object = { arg1, arg2, ... };
| (6) | |||
Функция ({ arg1, arg2, ... })
| (7) | |||
return { arg1, arg2, ... };
| (8) | |||
объект [{ arg1, arg2, ... }]
| (9) | |||
объект = { arg1, arg2, ... }
| (10) | |||
U ({ arg1, arg2, ... })
| (11) | |||
Класс { T member = { arg1, arg2, ... }; };
| (12) |
Инициализация списком выполняется в следующих ситуациях:
- прямая инициализация списком (рассматриваются как явные, так и неявно определённые конструкторы)
- копирующая инициализация списком (рассматриваются как явные, так и неявно определённые конструкторы, но вызываться могут только неявно определённые конструкторы)
return операторе с списком-инициализатором, используемым в качестве выражения возврата, и инициализация списком возвращаемого объектаoperator[], где инициализация списком инициализирует параметр перегруженного оператораОбъяснение
Эффекты инициализации списком объекта типа T:
| (с C++20) |
- Если
Tявляется агрегатным классом и список инициализации фигурными скобками, который не содержит список инициализаторов с указанием имени,(с 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 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}});
}
// 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}Сужающие преобразования
Инициализация списком ограничивает разрешённые неявные преобразования, запрещая следующие:
- преобразование с плавающей точкой в целое число
| (до C++23) |
| (с C++23) |
- преобразование из целого типа в тип с плавающей точкой, за исключением случаев, когда исходное значение является константным выражением, чьё значение может быть точно представлено в целевом типе
- преобразование из целого или неименованного перечисления в целое типа, который не может представить все значения исходного, за исключением случаев, когда
- исходное значение является полем битов, чья ширина
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.
| Макрос проверки функций | Значение | Std | Функция |
|---|---|---|---|
__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++.
| Отчёт о дефекте | Применён к | Поведение, опубликованное | Правильное поведение |
|---|---|---|---|
| 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 issue 1494 указало, что временные значения могли быть инициализированы напрямую списком | они инициализируются копированием, когда ссылки инициализируются списком |
| CWG 2374 | C++17 | прямая инициализация перечисления списком допускала слишком много типов исходных данных | ограничение |
| CWG 2627 | C++11 | узкое поле битов более крупного целого типа может быть преобразовано к меньшему целому типу, но это по-прежнему было преобразованием сужения | это не является преобразованием сужения |
| CWG 2713 | C++20 | ссылки на агрегатные классы не могли быть инициализированы инициализаторами списков с указанием | разрешено |
| 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/w/cpp/language/list_initialization