Namespaces
Variants

List-initialization (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Инициализирует объект из списка инициализации в фигурных скобках .

Содержание

Синтаксис

Прямая инициализация списком

T объект { арг1, арг2, ... };

T объект {. деск1 = арг1 , . деск2 { арг2 } ... };

(начиная с C++20)
(1)
T { арг1, арг2, ... }

T {. деск1 = арг1 , . деск2 { арг2 } ... }

(начиная с C++20)
(2)
new T { арг1, арг2, ... }

new T {. деск1 = арг1 , . деск2 { арг2 } ... }

(начиная с C++20)
(3)
Класс { T член { арг1, арг2, ... }; };

Класс { T член {. деск1 = арг1 , . деск2 { арг2 } ... }; };

(начиная с C++20)
(4)
Класс :: Класс () : член { арг1, арг2, ... } {...

Класс :: Класс () : член {. деск1 = арг1 , . деск2 { арг2 } ... } {...

(начиная с C++20)
(5)

Копирующая инициализация списком

T объект = { арг1, арг2, ... };

T объект = {. деск1 = арг1 , . деск2 { арг2 } ... };

(начиная с C++20)
(6)
функция ({ арг1, арг2, ... })

функция ({. деск1 = арг1 , . деск2 { арг2 } ... })

(начиная с C++20)
(7)
return { арг1, арг2, ... };

return {. деск1 = арг1 , . деск2 { арг2 } ... };

(начиная с C++20)
(8)
объект [{ арг1, арг2, ... }]

объект [{. деск1 = арг1 , . деск2 { арг2 } ... }]

(начиная с C++20)
(9)
объект = { арг1, арг2, ... }

объект = {. деск1 = арг1 , . деск2 { арг2 } ... }

(начиная с C++20)
(10)
U ({ арг1, арг2, ... })

U ({. деск1 = арг1 , . деск2 { арг2 } ... })

(начиная с C++20)
(11)
Класс { T член = { арг1, арг2, ... }; };

Класс { T член = {. деск1 = арг1 , . деск2 { арг2 } ... }; };

(начиная с C++20)
(12)

Инициализация списком выполняется в следующих ситуациях:

  • прямая инициализация списком (рассматриваются как явные, так и неявные конструкторы)
1) инициализация именованной переменной с помощью списка инициализации в фигурных скобках
2) инициализация безымянного временного объекта списком инициализации в фигурных скобках
3) инициализация объекта с динамической продолжительностью хранения с помощью new-expression , где инициализатор представляет собой список инициализации в фигурных скобках
4) в нестатическом data member initializer , который не использует знак равенства
5) в member initializer list конструктора, если используется список инициализации в фигурных скобках
  • copy-list-initialization (рассматриваются как explicit, так и non-explicit конструкторы, но вызываться могут только non-explicit конструкторы)
6) инициализация именованной переменной с помощью списка инициализации в фигурных скобках после знака равенства
7) в выражении вызова функции, когда в качестве аргумента используется список инициализации в фигурных скобках и список-инициализация инициализирует параметр функции
8) в return операторе с заключенным в фигурные скобки списком инициализации, используемым в качестве возвращаемого выражения, и когда list-initialization инициализирует возвращаемый объект
9) в выражении индексации с пользовательским operator[] , где list-initialization инициализирует параметр перегруженного оператора
10) в assignment expression , где list-initialization инициализирует параметр перегруженного оператора
11) функциональное приведение типа или другие вызовы конструкторов, где используется список инициализации в фигурных скобках вместо аргумента конструктора. Копирующая инициализация списком инициализирует параметр конструктора (примечание: тип U в этом примере не является типом, который инициализируется списком; U 's параметр конструктора является)
12) в нестатическом инициализаторе члена данных который использует знак равенства

Объяснение

Эффекты от list-инициализации объекта типа (возможно, cv-квалифицированного) T следующие:

  • Если список инициализации в фигурных скобках содержит список назначенных инициализаторов и T не является ссылочным типом, T должен быть агрегатным классом. Упорядоченные идентификаторы в дескрипторах списка назначенных инициализаторов должны образовывать подпоследовательность упорядоченных идентификаторов прямых нестатических элементов данных T . Агрегатная инициализация выполняется.
(начиная с C++20)
  • Если T является агрегатным классом и список инициализации в фигурных скобках , не содержащий designated initializer list, (начиная с C++20) имеет единственный элемент инициализации того же или производного типа (возможно, с cv-квалификаторами), объект инициализируется из этого элемента (через copy-initialization для copy-list-initialization, или через direct-initialization для direct-list-initialization).
  • Иначе, если T является массивом символов и список инициализации в фигурных скобках имеет единственный элемент инициализации, который является строковым литералом подходящего типа, массив инициализируется из строкового литерала как обычно .
  • В противном случае, если список инициализации в фигурных скобках пуст и T является классом с конструктором по умолчанию, value-initialization выполняется.
  • В противном случае, если T является специализацией std::initializer_list , объект инициализируется, как описано ниже .
  • В противном случае, если T является типом класса, конструкторы T рассматриваются в две фазы:
  • Все конструкторы, принимающие std::initializer_list в качестве единственного аргумента, или в качестве первого аргумента, если остальные аргументы имеют значения по умолчанию, рассматриваются и сопоставляются посредством overload resolution с единственным аргументом типа std::initializer_list .
  • Если предыдущая стадия не приводит к совпадению, все конструкторы T участвуют в разрешении перегрузки против набора аргументов, состоящего из инициализирующих выражений списка инициализации в фигурных скобках, с ограничением, что допускаются только непрeужающие преобразования. Если на этой стадии в качестве наилучшего соответствия для copy-list-инициализации выбран явный конструктор, компиляция завершается неудачей (обратите внимание, при простой copy-инициализации явные конструкторы вообще не рассматриваются).
  • В противном случае, если T является типом перечисления с фиксированным базовым типом U , список инициализации в фигурных скобках содержит только один инициализатор v , и выполняются все следующие условия, то перечисление инициализируется результатом преобразования v в U :
    • Инициализация является прямой списковой инициализацией (direct-list-initialization).
    • v имеет скалярный тип .
    • v неявно преобразуем в U .
    • Преобразование из v в U является непрeужающим (non-narrowing).
(начиная с C++17)
  • В противном случае (если T не является типом класса), если список инициализации в фигурных скобках содержит только один элемент инициализации и либо T не является ссылочным типом, либо является ссылочным типом, на который ссылается тип, совпадающий с типом элемента инициализации или являющийся его базовым классом, T подвергается прямой инициализации (при прямой инициализации списком) или копирующей инициализации (при копирующей инициализации списком), за исключением того, что сужающие преобразования не допускаются.
  • В противном случае, если T является ссылочным типом, не совместимым с типом инициализирующего выражения:
  • prvalue-временный объект типа, на который ссылается T , инициализируется copy-list-initialization, и ссылка привязывается к этому временному объекту (это не выполняется, если ссылка является неконстантной lvalue-ссылкой).
(до C++17)
  • генерируется prvalue. Prvalue инициализирует свой результирующий объект с помощью copy-list-initialization. Затем prvalue используется для direct-initialization ссылки (это не выполняется, если ссылка является неконстантной lvalue-ссылкой). Тип временного объекта — это тип, на который ссылается T , за исключением случая, когда T является «ссылкой на массив неизвестной границы U », в этом случае тип временного объекта — это тип x в объявлении U x [ ] H , где H — это список инициализации (начиная с C++20) .
(начиная с 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 базовые массивы с перекрывающимся временем жизни не могли перекрываться они могут перекрываться

Смотрите также