Spec-Zone.ru › C++

Инициализация списком (начиная с C++11)

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

Синтаксис

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

T object { arg1, arg2, ... };

T object{.des1 = arg1 , .des2 { arg2 } ... };

(начиная с C++20)
(1)
T { arg1, arg2, ... }

T {.des1 = arg1 , .des2 { arg2 } ... }

(начиная с C++20)
(2)
new T { arg1, arg2, ... }

new T {.des1 = arg1 , .des2 { arg2 } ... }

(начиная с C++20)
(3)
Class { T member { arg1, arg2, ... }; };

Class { T member {.des1 = arg1 , .des2 { arg2 } ... }; };

(начиная с C++20)
(4)
Class::Class() : member { arg1, arg2, ... } {...

Class::Class() : member {.des1 = arg1 , .des2 { arg2 } ...} {...

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

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

T object = { arg1, arg2, ... };

T object = {.des1 = arg1 , .des2 { arg2 } ... };

(начиная с C++20)
(6)
function ({ arg1, arg2, ... })

function ({.des1 = arg1 , .des2 { arg2 } ... })

(начиная с C++20)
(7)
return { arg1, arg2, ... };

return {.des1 = arg1 , .des2 { arg2 } ... };

(начиная с C++20)
(8)
object [{ arg1, arg2, ... }]

object [{.des1 = arg1 , .des2 { arg2 } ... }]

(начиная с C++20)
(9)
object = { arg1, arg2, ... }

object = {.des1 = arg1 , .des2 { arg2 } ... }

(начиная с C++20)
(10)
U ({ arg1, arg2, ... })

U ({.des1 = arg1 , .des2 { arg2 } ... })

(начиная с C++20)
(11)
Class { T member = { arg1, arg2, ... }; };

Class { T member = {.des1 = arg1 , .des2 { arg2 } ... }; };

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

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

  • прямая инициализация списком (рассматриваются как явные, так и неявные конструкторы)
1) инициализация именованной переменной списком инициализаторов в фигурных скобках
2) инициализация безымянного временного объекта списком инициализаторов в фигурных скобках
3) инициализация объекта с динамической длительностью хранения с помощью выражения new, где инициализатором служит список инициализаторов в фигурных скобках
4) в инициализаторе нестатического члена данных, в котором не используется знак равенства
5) в списке инициализации членов конструктора, если используется список инициализаторов в фигурных скобках
  • копирующая инициализация списком (рассматриваются как явные, так и неявные конструкторы, но вызываться могут только неявные конструкторы)
6) инициализация именованной переменной списком инициализаторов в фигурных скобках после знака равенства
7) в выражении вызова функции, где список инициализаторов в фигурных скобках используется как аргумент, а параметр функции инициализируется списком
8) в операторе return, где список инициализаторов в фигурных скобках используется как возвращаемое выражение, а возвращаемый объект инициализируется списком
9) в выражении индексирования с пользовательским operator[], где параметр перегруженного оператора инициализируется списком
10) в выражении присваивания, где параметр перегруженного оператора инициализируется списком
11) в выражении функционального приведения или при другом вызове конструктора, когда вместо аргумента конструктора используется список инициализаторов в фигурных скобках. Параметр конструктора инициализируется копирующим списком (примечание: тип U в этом примере не является типом, инициализируемым списком; инициализируется параметр конструктора U)
12) в инициализаторе нестатического члена данных, в котором используется знак равенства

Пояснение

Результат инициализации списком объекта типа (возможно, cv-квалифицированного) T:

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API