Namespaces
Variants

Aggregate initialization

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

Inicializa un agregado desde una lista de inicialización . Es una forma de list-initialization (since C++11) .

Contenidos

Sintaxis

T objeto = { arg1, arg2, ... }; (1)
T objeto { arg1, arg2, ... }; (2) (desde C++11)
T objeto = { . des1 = arg1 , . des2 { arg2 } ... }; (3) (desde C++20)
T objeto { . des1 = arg1 , . des2 { arg2 } ... }; (4) (desde C++20)
1,2) Inicialización de un agregado con una lista de inicialización ordinaria.
3,4) Inicializando un agregado con inicializadores designados (solo clases agregadas).

Definiciones

Agregado

Un aggregate es uno de los siguientes tipos:

  • tipos de array
  • tipos de clase que tiene
  • sin constructores declarados por el usuario
(hasta C++11)
(desde C++11)
(hasta C++20)
  • sin constructores declarados por el usuario o heredados
(desde C++20)
  • no hay miembros de datos no estáticos directos privados o protegidos
(hasta C++17)
(desde C++17)
  • sin funciones miembro virtuales
(desde C++11)
(hasta C++14)

Elemento

Los elementos de un agregado son:

  • para un array, los elementos del array en orden creciente de subíndice, o
  • para una clase, los miembros de datos no estáticos que no son bit-fields anónimos, en orden de declaración.
(until C++17)
  • para una clase, las clases base directas en orden de declaración, seguidas por los miembros de datos no estáticos directos que no son ni bit-fields anónimos ni miembros de una anonymous union , en orden de declaración.
(since C++17)

Pertenencia

Cada cláusula de inicialización en una lista de inicialización entre llaves se dice que corresponde a un elemento del agregado que se está inicializando o a un elemento de uno de sus subagregados.

Considerando la secuencia de cláusulas de inicialización, y la secuencia de elementos del agregado inicialmente formada como la secuencia de elementos del agregado que se está inicializando y potencialmente modificada como se describe a continuación:

  • Para cada cláusula inicializadora, si se satisface alguna de las siguientes condiciones, corresponde al elemento agregado correspondiente elem :
  • elem no es un agregado.
  • La cláusula de inicialización comienza con { .
  • La cláusula de inicialización es una expresión, y se puede formar una secuencia de conversión implícita que convierta la expresión al tipo de elem .
  • elem es un agregado que a su vez no tiene elementos agregados.
  • De lo contrario, elem es un agregado y ese subagregado se reemplaza en la lista de elementos del agregado por la secuencia de sus propios elementos agregados, y el análisis de pertenencia se reanuda con el primer elemento de este tipo y la misma cláusula de inicialización. En otras palabras, estas reglas se aplican recursivamente a los subagregados del agregado.

El análisis se completa cuando se han agotado todas las cláusulas de inicialización. Si queda alguna cláusula de inicialización que no corresponda a un elemento del agregado o a uno de sus subagregados, el programa está mal formado.

struct S1 { long a, b; };
struct S2 { S1 s, t; };
// Cada subagregado de “x” se asigna a una cláusula de inicialización que comienza con {
S2 x[2] =
{
    // se asigna a “x[0]”
    {
        {1L, 2L}, // se asigna a “x[0].s”
        {3L, 4L}  // se asigna a “x[0].t”
    },
    // se asigna a “x[1]”
    {
        {5L, 6L}, // se asigna a “x[1].s”
        {7L, 8L}  // se asigna a “x[1].t”
    }
};
// “x” e “y” tienen el mismo valor (ver abajo)
S2 y[2] = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L};
// El proceso del análisis de asignación de “y”:
// 1. Inicializa la secuencia de elementos agregados (x[0], x[1]) y
//    la secuencia de cláusulas de inicialización (1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L).
// 2. Comenzando desde los primeros elementos de cada secuencia,
//    verifica si 1L se asigna a x[0]:
//    · x[0] es un agregado.
//    · 1L no comienza con {.
//    · 1L es una expresión, pero no puede convertirse implícitamente a S2.
//    · x[0] tiene elementos agregados.
// 3. 1L no puede asignarse a x[0], por lo tanto x[0] se reemplaza por x[0].s y x[0].t,
//    la secuencia de elementos agregados se convierte en (x[0].s, x[0].t, x[1]).
// 4. Reanuda la verificación de asignación, pero 1L tampoco puede asignarse a x[0].s.
// 5. La secuencia de elementos agregados ahora se convierte en (x[0].s.a, x[0].s.b, x[0].t, x[1]).
// 6. Reanuda la verificación de asignación nuevamente:
//    1L se asigna a x[0].s.a, y 2L se asigna a x[0].s.b.
// 7. El resto del análisis de asignación funciona de manera similar.
char cv[4] = {'a', 's', 'd', 'f', 0}; // Error: demasiadas cláusulas de inicialización

Proceso de inicialización

Determinación del tipo de elemento

Los efectos de la inicialización de agregados son:

1) Determine los elementos explícitamente inicializados del agregado de la siguiente manera:
  • Si la lista de inicializadores es una lista de inicializadores designados (el agregado solo puede ser de tipo clase), el identificador en cada designador debe nombrar un miembro de datos directo no estático de la clase, y los elementos explícitamente inicializados del agregado son los elementos que son, o contienen, esos miembros.
(desde C++20)
  • De lo contrario, (desde C++20) si la lista de inicialización no está vacía, los elementos explícitamente inicializados del agregado son los elementos con una cláusula de inicialización correspondiente y los elementos que tienen un subagregado con una cláusula de inicialización correspondiente.
  • De lo contrario, la lista de inicialización debe estar vacía ( { } ), y no hay elementos explícitamente inicializados.
El programa está mal formado si el agregado es una unión y hay dos o más elementos inicializados explícitamente:
union u { int a; const char* b; };
u a = {1};                   // OK: inicializa explícitamente el miembro `a`
u b = {0, "asdf"};           // error: inicializa explícitamente dos miembros
u c = {"asdf"};              // error: int no puede ser inicializado por "asdf"
// C++20 listas de inicializadores designados
u d = {.b = "asdf"};         // OK: puede inicializar explícitamente un miembro no inicial
u e = {.a = 1, .b = "asdf"}; // error: inicializa explícitamente dos miembros
2) Inicializar cada elemento del agregado en orden de elementos. Es decir, todos los cálculos de valor y efectos secundarios asociados con un elemento dado están secuenciados antes que los de cualquier elemento que le siga en orden (since C++11) .

Elementos inicializados explícitamente

Para cada elemento inicializado explícitamente:

  • Si el elemento es un miembro de unión anónima y la lista de inicializadores es una lista de inicializadores designados , el elemento se inicializa mediante la lista de inicializadores designados { D } , donde D es la cláusula de inicializador designado que nombra un miembro de la unión anónima. Solo debe haber una única cláusula de inicializador designado de este tipo.
struct C
{
    union
    {
        int a;
        const char* p;
    };
    int x;
} c = {.a = 1, .x = 3}; // inicializa c.a con 1 y c.x con 3
  • En caso contrario, si la lista de inicializadores es una lista de inicializadores designados, el elemento se inicializa con el inicializador de la cláusula de inicializador designado correspondiente.
  • Si ese inicializador es de sintaxis (1) , y se requiere una conversión de estrechamiento para convertir la expresión, el programa está mal formado.
(desde C++20)


  • La lista de inicialización es una lista de inicialización entre llaves:
(until C++20)
  • En caso contrario, la lista de inicialización es una lista de inicialización entre llaves no designada:
(since C++20)
  • Si una cláusula inicializadora corresponde al elemento del agregado, entonces el elemento del agregado es copy-initialized desde la cláusula inicializadora.
  • De lo contrario, el elemento del agregado es copy-initialized desde una lista inicializadora entre llaves que consiste en todas las cláusulas inicializadoras que corresponden a subobjetos del elemento del agregado, en orden de aparición.
struct A
{
    int x;
    struct B
    {
        int i;
        int j;
    } b;
} a = {1, {2, 3}}; // inicializa a.x con 1, a.b.i con 2, a.b.j con 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}; // inicializa d1.b1 con 1, d1.b2 con 2,
                           //             d1.b3 con 42, d1.d con 4
derived d2{{}, {}, 4};     // inicializa d2.b1 con 0, d2.b2 con 42,
                           //             d2.b3 con 42, d2.d con 4

Elementos inicializados implícitamente

Para un agregado no unión, cada elemento que no es un elemento explícitamente inicializado se inicializa de la siguiente manera:

(since C++11)
  • De lo contrario, si el elemento no es una referencia, el elemento es copy-initialized desde una lista de inicializadores vacía.
  • De lo contrario, el programa está mal formado.
struct S
{
    int a;
    const char* b;
    int c;
    int d = b[a];
};
// inicializa ss.a con 1,
//             ss.b con "asdf",
//             ss.c con el valor de una expresión de la forma int{} (es decir, 0),
//         y ss.d con el valor de ss.b[ss.a] (es decir, 's')
S ss = {1, "asdf"};

Si el agregado es una unión y la lista de inicializadores está vacía, entonces

  • Si algún miembro variant tiene un inicializador de miembro predeterminado, ese miembro se inicializa desde su inicializador de miembro predeterminado.
(since C++11)
  • De lo contrario, el primer miembro de la unión (si existe) se inicializa por copia desde una lista de inicializadores vacía.

Arreglos con límites desconocidos

El número de elementos en un arreglo de límite desconocido inicializado con una lista de inicialización entre llaves es el número de elementos explícitamente inicializados del arreglo. Un arreglo de límite desconocido no puede ser inicializado con { } .

int x[] = {1, 3, 5}; // x tiene 3 elementos
struct Y { int i, j, k; };
Y y[] = {1, 2, 3, 4, 5, 6}; // y tiene solo 2 elementos:
                            // 1, 2 y 3 corresponden a y[0],
                            // 4, 5 y 6 corresponden a y[1]
int z[] = {} // Error: no se puede declarar un array sin elementos

Inicializadores designados

Las formas de sintaxis (3,4) se conocen como inicializadores designados: cada designador debe nombrar un miembro de datos directo no estático de T, y todos los designadores utilizados en la expresión deben aparecer en el mismo orden que los miembros de datos de T.

struct A { int x; int y; int z; };
A a{.x = 1, .y = 2, .z = 3}; // ok
A b{.y = 2, .z = 3, .x = 1}; // error; designator order does not match declaration order

Cada miembro de datos directo no estático nombrado por el inicializador designado se inicializa desde el inicializador entre llaves o igual correspondiente que sigue al designador. Se prohíben las conversiones que reduzcan el rango.

El inicializador designado puede usarse para inicializar una union en un estado diferente al primero. Solo se puede proporcionar un inicializador para una union.

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

Para un agregado no union, los elementos para los cuales no se proporciona un inicializador designado se inicializan de la misma manera descrita anteriormente para cuando el número de cláusulas de inicialización es menor que el número de miembros (inicializadores de miembros predeterminados donde se proporcionaron, inicialización de lista vacía en caso contrario):

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
struct A { int x; int y; int z; };
A a{.x = 1, .z = 2}; // ok, b.y initialized to 0
A b{.y = 2, .x = 1}; // error; designator order does not match declaration order
A c{.y = 2}; // ok, c.x and c.z are initialized to 0
constexpr A d{.z = 2}; // can be used with constexpr, as opposed to: constexpr A d;
static_assert(d.x == 0 && d.y == 0); // d.x and d.y are initialized to 0

Si el agregado que se inicializa con una cláusula de inicializador designado tiene un miembro union anónimo, el inicializador designado correspondiente debe nombrar uno de los miembros de esa union anónima.

Nota: la inicialización designada fuera de orden, la inicialización designada anidada, la mezcla de inicializadores designados e inicializadores regulares, y la inicialización designada de arrays están todas soportadas en el lenguaje de programación C , pero no están permitidas en 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)
(desde C++20)

Arreglos de caracteres

Los arreglos de tipos de caracteres ordinarios ( char , signed char , unsigned char ) , char8_t (desde C++20) , char16_t , char32_t (desde C++11) , o wchar_t pueden inicializarse desde literales de cadena ordinarios , literales de cadena UTF-8 (desde C++20) , literales de cadena UTF-16, literales de cadena UTF-32 (desde C++11) , respectivamente, opcionalmente encerrados entre llaves . Adicionalmente, un arreglo de char o unsigned char puede ser inicializado por un literal de cadena UTF-8, opcionalmente encerrado entre llaves (desde C++20) . Los caracteres sucesivos del literal de cadena (que incluye el carácter nulo terminador implícito) inicializan los elementos del arreglo , con una conversión integral si es necesario para el valor fuente y destino (desde C++20) . Si se especifica el tamaño del arreglo y es mayor que el número de caracteres en el literal de cadena, los caracteres restantes se inicializan a cero.

char a[] = "abc";
// equivalente a char a[4] = {'a', 'b', 'c', '\0'};
//  unsigned char b[3] = "abc"; // Error: cadena inicializadora demasiado larga
unsigned char b[5]{"abc"};
// equivalente a unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'};
wchar_t c[] = {L"кошка"}; // llaves opcionales
// equivalente a wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};

Notas

Una clase o arreglo agregado puede incluir bases públicas no agregadas (desde C++17) , miembros o elementos, que se inicializan como se describió anteriormente (por ejemplo, inicialización por copia desde la cláusula inicializadora correspondiente).

Hasta C++11, se permitían conversiones de estrechamiento en la inicialización de agregados, pero ya no están permitidas.

Hasta C++11, la inicialización de agregados solo podía utilizarse en la definición de variables, y no podía usarse en una lista de inicialización de constructores , una expresión new , o la creación de objetos temporales debido a restricciones de sintaxis.

En C, un arreglo de caracteres de tamaño uno menos que el tamaño del literal de cadena puede ser inicializado desde un literal de cadena; el arreglo resultante no está terminado en nulo. Esto no está permitido en C++.

Macro de prueba de características Valor Estándar Característica
__cpp_aggregate_bases 201603L (C++17) Clases agregadas con clases base
__cpp_aggregate_nsdmi 201304L (C++14) Clases agregadas con inicializadores de miembros predeterminados
__cpp_aggregate_paren_init 201902L (C++20) Inicialización agregada en forma de inicialización directa
__cpp_char8_t 202207L (C++23)
(DR20)
char8_t corrección de compatibilidad y portabilidad ( permitir inicialización de arrays ( unsigned char ) desde literales de cadena UTF-8 )
__cpp_designated_initializers 201707L (C++20) Inicializadores designados

Ejemplo

#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}; // igual, pero con elisión de llaves
    S s3{1, {2, 3, {4, 5, 6}}}; // igual, usando sintaxis de inicialización directa por lista
    S s4{1, 2, 3, 4, 5, 6}; // error hasta CWG 1270:
                            // la elisión de llaves solo se permite con signo igual
    int ar[] = {1, 2, 3}; // ar es int[3]
//  char cr[3] = {'a', 'b', 'c', 'd'}; // demasiadas cláusulas inicializadoras
    char cr[3] = {'a'}; // array inicializado como {'a', '\0', '\0'}
    int ar2d1[2][2] = {{1, 2}, {3, 4}}; // array 2D completamente entre llaves: {1, 2}
                                        //                        {3, 4}
    int ar2d2[2][2] = {1, 2, 3, 4}; // elisión de llaves: {1, 2}
                                    //                {3, 4}
    int ar2d3[2][2] = {{1}, {2}};   // solo primera columna: {1, 0}
                                    //                    {2, 0}
    std::array<int, 3> std_ar2{{1, 2, 3}};  // std::array es un agregado
    std::array<int, 3> std_ar1 = {1, 2, 3}; // elisión de llaves correcta
//  int ai[] = {1, 2.0}; // conversión estrechante de double a int:
                         // error en C++11, correcto en C++03
    std::string ars[] = {std::string("one"), // inicialización por copia
                         "two",              // conversión, luego inicialización por copia
                         {'t', 'h', 'r', 'e', 'e'}}; // inicialización por lista
    union U
    {
        int a;
        const char* b;
    };
    U u1 = {1};         // OK, primer miembro de la unión
//  U u2 = {0, "asdf"}; // error: demasiados inicializadores para unión
//  U u3 = {"asdf"};    // error: conversión inválida a int
    [](...) { std::puts("Garbage collecting unused variables... Done."); }
    (
        s1, s2, s3, s4, ar, cr, ar2d1, ar2d2, ar2d3, std_ar2, std_ar1, u1
    );
}
// agregado
struct base1 { int b1, b2 = 42; };
// no agregado
struct base2
{
    base2() : b3(42) {}
    int b3;
};
// agregado en 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

Salida:

Garbage collecting unused variables... Done.

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Se aplica a Comportamiento publicado Comportamiento correcto
CWG 413 C++98 los campos de bits anónimos se inicializaban en la inicialización de agregados se ignoran
CWG 737 C++98 cuando un array de caracteres se inicializa con un literal de cadena
que tiene menos caracteres que el tamaño del array, los elementos
de caracteres después del ' \0 ' final permanecían sin inicializar
se inicializan
a cero
CWG 1270 C++11 la elisión de llaves solo estaba permitida en la inicialización de lista de copia permitida en otros contextos
CWG 1518 C++11 una clase que declara un constructor por defecto explícito o
tiene constructores heredados podría ser un agregado
no es un
agregado
CWG 1622 C++98 una unión no podía inicializarse con { } permitido
CWG 2149
( P3106R1 )
C++98 no estaba claro si la elisión de llaves es
aplicable durante la deducción del tamaño del array
aplicable
CWG 2272 C++98 un miembro de referencia no estático que no se inicializa
explícitamente se inicializaba por copia desde una lista de inicializadores vacía
el programa está
mal formado en este caso
CWG 2610 C++17 los tipos agregados no podían tener clases base indirectas privadas o protegidas permitido
CWG 2619 C++20 el tipo de inicialización desde inicializadores designados no estaba claro depende del
tipo del inicializador
P2513R4 C++20 un literal de cadena UTF-8 no podía inicializar un array de char
o unsigned char , lo que era incompatible con C o C++17
dicha inicialización
es válida

Véase también

Documentación de C para Inicialización de estructuras y uniones