Spec-Zone.ru › C++

Агрегатная инициализация

Инициализирует агрегат списком инициализаторов. Это форма списковой инициализации(начиная с C++11).

Синтаксис

T object= { arg1, arg2, ... }; (1)
T object { arg1, arg2, ... }; (2) (начиная с C++11)
T object= { .des1=arg1 , .des2 { arg2 } ... }; (3) (начиная с C++20)
T object { .des1=arg1 , .des2 { arg2 } ... }; (4) (начиная с C++20)
1,2) Инициализация агрегата обычным списком инициализаторов.
3,4) Инициализация агрегата с помощью обозначенных инициализаторов (только для агрегатного класса).

Определения

Агрегат

Агрегат — это один из следующих типов:

  • типы массивов
  • типы классов, которые имеют
  • отсутствие объявленных пользователем конструкторов
(до C++11)
  • отсутствие конструкторов, определённых пользователем, унаследованных или явных
(начиная с C++11)
(до C++20)
  • отсутствие объявленных пользователем или унаследованных конструкторов
(начиная с C++20)
  • отсутствие закрытых или защищённых прямых нестатических членов данных
  • отсутствие базовых классов
(до C++17)
  • отсутствие виртуальных базовых классов
  • отсутствие прямых закрытых или защищённых базовых классов
(начиная с C++17)
  • отсутствие виртуальных функций-членов
  • отсутствие инициализаторов членов по умолчанию
(начиная с C++11)
(до C++14)

Элемент

Элементы агрегата — это:

  • для массива — элементы массива в порядке возрастания индексов либо
  • для класса — нестатические члены данных, не являющиеся анонимными битовыми полями, в порядке объявления.
(до C++17)
  • для класса — прямые базовые классы в порядке объявления, за которыми следуют прямые нестатические члены данных, которые не являются анонимными битовыми полями и не являются членами анонимного объединения, в порядке объявления.
(начиная с C++17)

Соответствие элементу

Каждая инициализирующая часть в списке инициализаторов, заключённом в фигурные скобки, считается относящейся к элементу инициализируемого агрегата или к элементу одного из его подагрегатов.

Рассмотрим последовательность инициализирующих частей и последовательность элементов агрегата, изначально сформированную как последовательность элементов инициализируемого агрегата и потенциально изменяемую, как описано ниже:

  • Для каждой инициализирующей части, если выполняется любое из следующих условий, она относится к соответствующему элементу агрегата elem:
    • elem не является агрегатом.
    • Инициализирующая часть начинается с {.
    • Инициализирующая часть является выражением, и может быть сформирована последовательность неявного преобразования, преобразующая это выражение к типу elem.
    • elem является агрегатом, который сам не имеет элементов агрегата.
  • В противном случае elem является агрегатом, и этот подагрегат заменяется в списке элементов агрегата последовательностью собственных элементов агрегата; анализ соответствия элементам продолжается с первым таким элементом и той же инициализирующей частью. Иными словами, эти правила рекурсивно применяются к подагрегатам агрегата.

Анализ завершается, когда исчерпаны все инициализирующие части. Если остаётся инициализирующая часть, не относящаяся ни к элементу агрегата, ни к элементу одного из его подагрегатов, программа некорректна.

struct S1 { int a, b; };
struct S2 { S1 s, t; };

// Each subaggregate of “x” is appertained to an initializer clause starting with {
S2 x[2] =
{
    // appertains to “x[0]”
    {
        {1, 2}, // appertains to “x[0].s”
        {3, 4}  // appertains to “x[0].t”
    },
    // appertains to “x[1]”
    {
        {5, 6}, // appertains to “x[1].s”
        {7, 8}  // appertains to “x[1].t”
    }
};

// “x” and “y” have the same value (see below)
S2 y[2] = {1, 2, 3, 4, 5, 6, 7, 8};

// The process of the appertainment analysis of “y”:
// 1. Initializes the aggregate element sequence (x[0], x[1]) and
//    the initializer clause sequence (1, 2, 3, 4, 5, 6, 7, 8).
// 2. Starting from the first elements of each sequence,
//    checks whether 1 appertains to x[0]:
//    · x[0] is an aggregate.
//    · 1 does not begin with {.
//    · 1 is an expression, but it cannot be implicitly converted to S2.
//    · x[0] has aggregate elements.
// 3. 0 cannot appertain to x[0], therefore x[0] is replaced by x[0].s and x[0].t,
//    the aggregate element sequence becomes (x[0].s, x[0].t, x[1]).
// 4. Resumes the appertainment check, but 1 cannot appertain to x[0].s either.
// 5. The aggregate element sequence now becomes (x[0].s.a, x[0].s.b, x[0].t, x[1]).
// 6. Resumes the appertainment check again:
//    1 appertains to x[0].s.a, and 2 appertains to x[0].s.b.
// 7. The rest of the appertainment analysis works similarly.

char cv[4] = {'a', 's', 'd', 'f', 0}; // Error: too many initializer clauses

Процесс инициализации

Определение вида элемента

Эффекты агрегатной инициализации:

1) Определить явно инициализированные элементы агрегата следующим образом:
  • Если список инициализаторов является списком обозначенных инициализаторов (агрегат может иметь только тип класса), идентификатор в каждом обозначении должен задавать прямой нестатический член данных класса, а явно инициализированными элементами агрегата являются элементы, которые являются такими членами или содержат их.
(начиная с C++20)
  • В противном случае, если список инициализаторов не пуст, явно инициализированными элементами агрегата являются элементы, которым соответствует инициализирующая часть, и элементы, имеющие подагрегат с соответствующей инициализирующей частью.
  • В противном случае список инициализаторов должен быть пустым ({}), и явно инициализированных элементов нет.
Программа некорректна, если агрегат является объединением и имеет два или более явно инициализированных элемента:
union u { int a; const char* b; };

u a = {1};                   // OK: explicitly initializes member `a`
u b = {0, "asdf"};           // error: explicitly initializes two members
u c = {"asdf"};              // error: int cannot be initialized by "asdf"

// C++20 designated initializer lists
u d = {.b = "asdf"};         // OK: can explicitly initialize a non-initial member
u e = {.a = 1, .b = "asdf"}; // error: explicitly initializes two members
2) Инициализировать каждый элемент агрегата в порядке следования элементов. То есть все вычисления значений и побочные эффекты, связанные с данным элементом, упорядочены до вычислений и побочных эффектов любого следующего за ним элемента(начиная с C++11).

Явно инициализированные элементы

Для каждого явно инициализированного элемента:

  • Если элемент является членом анонимного объединения, а список инициализаторов является списком обозначенных инициализаторов, элемент инициализируется списком обозначенных инициализаторов {D}, где D — обозначенная инициализирующая часть, задающая член объединения, входящего в анонимное объединение. Такая обозначенная инициализирующая часть должна быть только одна.
struct C
{
    union
    {
        int a;
        const char* p;
    };
    
    int x;
} c = {.a = 1, .x = 3}; // initializes c.a with 1 and c.x with 3
  • В противном случае, если список инициализаторов является списком обозначенных инициализаторов, элемент инициализируется инициализатором соответствующей обозначенной инициализирующей части.
    • Если этот инициализатор имеет синтаксис (1) и для преобразования выражения требуется сужающее преобразование, программа некорректна.
(начиная с C++20)
  • Список инициализаторов представляет собой список инициализаторов, заключённый в фигурные скобки:
(до C++20)
  • В противном случае список инициализаторов представляет собой список инициализаторов, заключённый в фигурные скобки и не содержащий обозначений:
(начиная с C++20)
  • Если инициализирующая часть относится к элементу агрегата, элемент агрегата копирующе инициализируется из этой инициализирующей части.
  • В противном случае элемент агрегата копирующе инициализируется списком инициализаторов в фигурных скобках, состоящим из всех инициализирующих частей, относящихся к подобъектам этого элемента агрегата, в порядке их появления.
struct A
{
    int x;
    
    struct B
    {
        int i;
        int j;
    } b;
} a = {1, {2, 3}}; // initializes a.x with 1, a.b.i with 2, a.b.j with 3

struct base1 { int b1, b2 = 42; };

struct base2
{
    base2()
    {
        b3 = 42;
    }
    
    int b3;
};

struct derived : base1, base2
{
    int d;
};

derived d1{{1, 2}, {}, 4}; // initializes d1.b1 with 1, d1.b2 with 2,
                           //             d1.b3 with 42, d1.d with 4
derived d2{{}, {}, 4};     // initializes d2.b1 with 0, d2.b2 with 42,
                           //             d2.b3 with 42, d2.d with 4

Неявно инициализированные элементы

Для не являющегося объединением агрегата каждый элемент, который не является явно инициализированным, инициализируется следующим образом:

  • Если у элемента есть инициализатор члена по умолчанию, элемент инициализируется этим инициализатором.
(начиная с C++11)
  • В противном случае, если элемент не является ссылкой, он копирующе инициализируется пустым списком инициализаторов.
  • В противном случае программа некорректна.
struct S
{
    int a;
    const char* b;
    int c;
    int d = b[a];
};

// initializes ss.a with 1,
//             ss.b with "asdf",
//             ss.c with the value of an expression of the form int{} (that is, 0),
//         and ss.d with the value of ss.b[ss.a] (that is, 's')
S ss = {1, "asdf"};

Если агрегат является объединением, а список инициализаторов пуст, то

  • Если у какого-либо вариативного члена есть инициализатор члена по умолчанию, этот член инициализируется своим инициализатором по умолчанию.
(начиная с C++11)
  • В противном случае первый член объединения (если он есть) копирующе инициализируется пустым списком инициализаторов.

Массивы с неизвестными границами

Количество элементов массива с неизвестной границей, инициализируемого списком инициализаторов в фигурных скобках, равно количеству явно инициализированных элементов массива. Массив с неизвестной границей нельзя инициализировать с помощью {}.

int x[] = {1, 3, 5}; // x has 3 elements

struct Y { int i, j, k; };

Y y[] = {1, 2, 3, 4, 5, 6}; // y has only 2 elements:
                            // 1, 2 and 3 appertain to y[0],
                            // 4, 5 and 6 appertain to y[1]

int z[] = {} // Error: cannot declare an array without any element

Обозначенные инициализаторы

Синтаксические формы (3,4) называются обозначенными инициализаторами: каждое обозначение должно задавать прямой нестатический член данных T, а все используемые в выражении обозначения должны располагаться в том же порядке, что и члены данных T.

struct A { int x; int y; int z; };

A a{.y = 2, .x = 1}; // error; designator order does not match declaration order
A b{.x = 1, .z = 2}; // ok, b.y initialized to 0

Каждый прямой нестатический член данных, указанный обозначенным инициализатором, инициализируется соответствующим инициализатором в фигурных скобках или после знака равенства, следующим за обозначением. Сужающие преобразования запрещены.

Обозначенный инициализатор можно использовать, чтобы инициализировать объединение в состояние, отличное от состояния первого члена. Для объединения можно указать только один инициализатор.

union u { int a; const char* b; };

u f = {.b = "asdf"};         // OK, active member of the union is b
u g = {.a = 1, .b = "asdf"}; // Error, only one initializer may be provided

Для агрегата, не являющегося объединением, элементы, для которых не указан обозначенный инициализатор, инициализируются так же, как описано выше для случая, когда количество инициализирующих частей меньше количества членов (с использованием инициализаторов членов по умолчанию, если они заданы, и пустой списковой инициализации в противном случае):

struct A
{
    string str;
    int n = 42;
    int m = -1;
};

A{.m = 21} // Initializes str with {}, which calls the default constructor
           // then initializes n with = 42
           // then initializes m with = 21

Если агрегат, инициализируемый обозначенной инициализирующей частью, имеет член — анонимное объединение, соответствующий обозначенный инициализатор должен задавать один из членов этого анонимного объединения.

Примечание: инициализация обозначенными инициализаторами не по порядку объявления, вложенная инициализация обозначенными инициализаторами, смешивание обозначенных и обычных инициализаторов, а также инициализация массивов обозначенными инициализаторами поддерживаются в языке программирования C, но не разрешены в C++.

struct A { int x, y; };
struct B { struct A a; };

struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order)
int arr[3] = {[1] = 5};        // valid C, invalid C++ (array)
struct B b = {.a.x = 0};       // valid C, invalid C++ (nested)
struct A a = {.x = 1, 2};      // valid C, invalid C++ (mixed)
(начиная с C++20)

Массивы символов

Массивы обычных символьных типов (char, signedchar, unsignedchar), char8_t(начиная с C++20), char16_t, char32_t(начиная с C++11), или wchar_t могут инициализироваться обычными строковыми литералами, строковыми литералами UTF-8(начиная с C++20), строковыми литералами UTF-16 и UTF-32(начиная с C++11) или широкими строковыми литералами соответственно, при необходимости заключёнными в фигурные скобки. Кроме того, массив char или unsignedchar может инициализироваться строковым литералом UTF-8, при необходимости заключённым в фигурные скобки(начиная с C++20). Последовательные символы строкового литерала (включая неявный завершающий нулевой символ) инициализируют элементы массива, при необходимости с выполнением целочисленного преобразования исходного значения в целевое(начиная с C++20). Если размер массива задан и он больше количества символов в строковом литерале, оставшиеся символы инициализируются нулём.

char a[] = "abc";
// equivalent to char a[4] = {'a', 'b', 'c', '\0'};

//  unsigned char b[3] = "abc"; // Error: initializer string too long
unsigned char b[5]{"abc"};
// equivalent to unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'};

wchar_t c[] = {L"кошка"}; // optional braces
// equivalent to wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};

Примечания

Агрегатный класс или массив может содержать неагрегатные открытые базовые классы(начиная с C++17), члены или элементы, которые инициализируются описанным выше способом (например, копирующей инициализацией из соответствующей инициализирующей части).

До C++11 сужающие преобразования при агрегатной инициализации разрешались, но теперь они запрещены.

До C++11 агрегатную инициализацию можно было использовать только при определении переменной; из-за ограничений синтаксиса её нельзя было использовать в списке инициализаторов конструктора, выражении new или при создании временного объекта.

В C массив символов размером на единицу меньше длины строкового литерала можно инициализировать строковым литералом; полученный массив не завершается нулевым символом. В C++ это не разрешено.

Макрос проверки поддержки функции Значение Стандарт Функция
__cpp_aggregate_bases 201603L (C++17) Агрегатные классы с базовыми классами
__cpp_aggregate_nsdmi 201304L (C++14) Агрегатные классы с инициализаторами членов по умолчанию
__cpp_aggregate_paren_init 201902L (C++20) Агрегатная инициализация в форме прямой инициализации
__cpp_char8_t 202207L (C++23)
(DR20)
Совместимость и переносимость char8_t (разрешение инициализации массивов unsignedchar из строковых литералов UTF-8)
__cpp_designated_initializers 201707L (C++20) Обозначенные инициализаторы

Пример

#include <array>
#include <cstdio>
#include <string>

struct S
{
    int x;
    
    struct Foo
    {
        int i;
        int j;
        int a[3];
    } b;
};

int main()
{
    S s1 = {1, {2, 3, {4, 5, 6}}};
    S s2 = {1, 2, 3, 4, 5, 6}; // same, but with brace elision
    S s3{1, {2, 3, {4, 5, 6}}}; // same, using direct-list-initialization syntax
    S s4{1, 2, 3, 4, 5, 6}; // error until CWG 1270:
                            // brace elision only allowed with equals sign

    int ar[] = {1, 2, 3}; // ar is int[3]
//  char cr[3] = {'a', 'b', 'c', 'd'}; // too many initializer clauses
    char cr[3] = {'a'}; // array initialized as {'a', '\0', '\0'}

    int ar2d1[2][2] = {{1, 2}, {3, 4}}; // fully-braced 2D array: {1, 2}
                                        //                        {3, 4}
    int ar2d2[2][2] = {1, 2, 3, 4}; // brace elision: {1, 2}
                                    //                {3, 4}
    int ar2d3[2][2] = {{1}, {2}};   // only first column: {1, 0}
                                    //                    {2, 0}

    std::array<int, 3> std_ar2{{1, 2, 3}};  // std::array is an aggregate
    std::array<int, 3> std_ar1 = {1, 2, 3}; // brace-elision okay

//  int ai[] = {1, 2.0}; // narrowing conversion from double to int:
                         // error in C++11, okay in C++03

    std::string ars[] = {std::string("one"), // copy-initialization
                         "two",              // conversion, then copy-initialization
                         {'t', 'h', 'r', 'e', 'e'}}; // list-initialization
    union U
    {
        int a;
        const char* b;
    };
    U u1 = {1};         // OK, first member of the union
//  U u2 = {0, "asdf"}; // error: too many initializers for union
//  U u3 = {"asdf"};    // error: invalid conversion to int

    [](...) { std::puts("Garbage collecting unused variables... Done."); }
    (
        s1, s2, s3, s4, ar, cr, ar2d1, ar2d2, ar2d3, std_ar2, std_ar1, u1
    );
}

// aggregate
struct base1 { int b1, b2 = 42; };

// non-aggregate
struct base2
{
    base2() : b3(42) {}
    
    int b3;
};

// aggregate in C++17
struct derived : base1, base2 { int d; };

derived d1{{1, 2}, {}, 4}; // d1.b1 = 1, d1.b2 = 2,  d1.b3 = 42, d1.d = 4
derived d2{{}, {}, 4};     // d2.b1 = 0, d2.b2 = 42, d2.b3 = 42, d2.d = 4

Вывод:

Garbage collecting unused variables... Done.

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.

DR Применён к Поведение согласно опубликованной версии Корректное поведение
CWG 413 C++98 анонимные битовые поля инициализировались при агрегатной инициализации они игнорируются
CWG 737 C++98 если массив символов инициализировался строковым литералом,
содержащим меньше символов, чем размер массива, символьные
элементы после завершающего '\0' оставались неинициализированными
они
инициализируются нулём
CWG 1270 C++11 опускать фигурные скобки разрешалось только при копирующей списковой инициализации разрешено и в других случаях
CWG 1518 C++11 класс, объявляющий явный конструктор по умолчанию или
имеющий унаследованные конструкторы, мог быть агрегатом
он не является
агрегатом
CWG 1622 C++98 объединение нельзя было инициализировать с помощью {} разрешено
CWG 2149
(P3106R1)
C++98 не было ясно, применимо ли опускание фигурных скобок
при выводе размера массива
применимо
CWG 2272 C++98 нестатический член-ссылка, который явно не
инициализирован, копирующе инициализировался пустым списком инициализаторов
в этом случае программа
некорректна
CWG 2610 C++17 агрегатные типы не могли иметь закрытые или защищённые косвенные базовые классы разрешено
CWG 2619 C++20 вид инициализации обозначенными инициализаторами был неясен он зависит от
вида инициализатора
P2513R4 C++20 строковый литерал UTF-8 не мог инициализировать массив char
или unsignedchar, что было несовместимо с C и C++17
такая инициализация
допустима

См. также

  • устранение копирования
  • инициализация
    • константная инициализация
    • списковая инициализация
    • инициализация ссылки
    • инициализация значением
    • инициализация нулём
Документация по C для Инициализация структур и объединений

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/aggregate_initialization

Spec-Zone.ru

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