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)
Класс { T member { arg1, arg2, ... }; };

Класс { T member {.des1 = arg1 , .des2 { arg2 } ... }; };

(с C++20)
(4)
Класс::Класс() : member { arg1, arg2, ... } {...

Класс::Класс() : member {.des1 = arg1 , .des2 { arg2 } ...} {...

(с C++20)
(5)

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

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

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

(с C++20)
(6)
Функция ({ arg1, arg2, ... })

Функция ({.des1 = arg1 , .des2 { arg2 } ... })

(с C++20)
(7)
return { arg1, arg2, ... };

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

(с C++20)
(8)
объект [{ arg1, arg2, ... }]

объект [{.des1 = arg1 , .des2 { arg2 } ... }]

(с C++20)
(9)
объект = { arg1, arg2, ... }

объект = {.des1 = arg1 , .des2 { arg2 } ... }

(с C++20)
(10)
U ({ arg1, arg2, ... })

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

(с C++20)
(11)
Класс { T member = { arg1, arg2, ... }; };

Класс { 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) в нестатическом инициализаторе данных члена, который использует знак равенства

Объяснение

Эффекты инициализации списком объекта типа T:

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

Сужающие преобразования

Инициализация списком ограничивает разрешённые неявные преобразования, запрещая следующие:

  • преобразование с плавающей точкой в целое число
  • преобразование из long double в double или в float, а также преобразование из double в float, за исключением случаев, когда исходное значение является константным выражением и переполнение не происходит
(до C++23)
  • преобразование из типа с плавающей точкой T в другой тип с плавающей точкой, чья степень преобразования типа с плавающей точкой не больше и не равна степени T, за исключением случаев, когда исходное значение является константным выражением и переполнение не происходит
(с 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

Spec-Zone.ru

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