Inicialización de lista (desde C++11)
Inicializa un objeto a partir de una lista de inicializadores entre llaves.
Sintaxis
Inicialización de lista directa
T objeto { arg1, arg2, ... };
|
(1) | ||||||||
T { arg1, arg2, ... }
|
(2) | ||||||||
new T { arg1, arg2, ... }
|
(3) | ||||||||
Clase { T miembro { arg1, arg2, ... }; };
|
(4) | ||||||||
Clase::Clase() : miembro{arg1, arg2, ...} {...
|
(5) | ||||||||
Inicialización de lista de copia
T objeto = {arg1, arg2, ...};
|
(6) | ||||||||
función( { arg1, arg2, ... } )
|
(7) | ||||||||
return { arg1, arg2, ... } ;
|
(8) | ||||||||
objeto[ { arg1, arg2, ... } ]
|
(9) | ||||||||
objeto = { arg1, arg2, ... }
|
(10) | ||||||||
U( { arg1, arg2, ... } )
|
(11) | ||||||||
Clase { T miembro = { arg1, arg2, ... }; };
|
(12) | ||||||||
La inicialización de lista se lleva a cabo en las siguientes situaciones:
- Inicialización de lista directa (se consideran los constructores explícitos como los no explícitos):
- Inicialización de lista de copia (se consideran tanto los constructores tanto explícitos como no explícitos, pero solamente puede llamarse a los constructores no explícitos):
return con una lista de inicializadores entre llaves usada como la expresión de retorno y la inicialización de lista inicializa el objeto devuelto;operator[] definido por el usuario, donde la inicialización de lista inicializa el parámetro del operador sobrecargado;U en este ejemplo no es el tipo que se está inicializando mediante la inicialización de lista, sino el parámetro del constructor de U);Explicación
Los efectos de la inicialización de lista de un objeto de tipo T son:
|
(desde C++14) |
|
(hasta C++14) |
|
(desde C++14) |
- De lo contrario, si
Tes una especialización de std::initializer_list, el objetoTse inicializa mediante la inicialización directa o la inicialización de copia, dependiendo del contexto, a partir de un prvalue del mismo tipo inicializado a partir de (hasta C++17) la lista de inicializadores entre llaves.
- De lo contrario, se consideran los constructores de
Ten dos fases:
- Se examinan todos los constructores que toman una std::initializer_list como su único argumento, o como el primer argumento si los argumentos restantes tienen valores por defecto, y se coinciden mediante la resolución de sobrecarga frente a un solo argumento de tipo std::initializer_list
- Si la etapa previa no produce una coincidencia, todos los constructores de
Tparticipan en la resolución de sobrecarga frente al conjunto de argumentos que consiste en los elementos de la lista de inicializadores entre llaves, con la restricción que solamente se permiten las conversiones no estrechantes. Si esta etapa produce un constructor explícito como la mejor coincidencia para una inicialización de lista de copia, la compilación falla (observa que en una simple inicialización de copia, los constructores explícitos no se consideran para nada).
- Si la etapa previa no produce una coincidencia, todos los constructores de
|
(desde C++17) |
- De lo contrario (si
Tno es un tipo clase), si la lista de inicializadores entre llaves solamente tiene un elemento y ya sea queTno es un tipo referencia o es un tipo referencia cuyo tipo referenciado es el mismo que, o es una clase base del tipo del elemento,Tse inicializa mediante la inicialización directa (en la inicialización de lista directa) o la inicialización de copia (en la inicialización de lista de copia), excepto que no se permiten las conversiones estrechantes.
- De lo contrario, si
Tes un tipo referencia que no es compatible con el tipo del elemento, se inicializa un temporal del tipo referenciado o su tipo de array correspondiente de límite conocido (desde C++20) mediante la inicialización de lista, y la referencia se vincula al temporal (esto falla si la referencia es una referencia lvalue noconst).
|
(desde C++20) |
- De lo contrario, si la lista de inicializadores entre llaves no tiene elementos,
Tse inicializa mediante la inicialización de un valor.
Conversiones de estrechamiento
La inicialización de lista limita las conversiones implícitas permitidas prohibiendo lo siguiente:
- la conversión de un tipo de punto flotante a un tipo entero;
- la conversión de un
long doubleadoubleo afloaty la conversión dedoubleafloat, excepto donde la fuente es una expresión constante y no ocurre desbordamiento;
- la conversión de un tipo entero a un tipo de punto flotante, excepto donde la fuente es una expresión constante cuyo valor puede almacenarse exactamente en el tipo destino;
- la conversión de un tipo entero o de enumeración sin ámbito a un tipo entero que no puede representar todos los valores del original, excepto donde la fuente es una expresión constante cuyo valor puede almacenarse exactamente en el tipo destino;
|
(desde C++20) |
Notas
Cada cláusula de inicializador está secuenciada antes que cualquier cláusula de inicializador que la sucede en la lista de inicializadores entre llaves. Esto contrasta con los argumentos de una expresión de llamada a función, que están sin secuenciar.
Una lista de inicializadores entre llaves no es una expresión y por lo tanto no tiene tipo. Por ejemplo, decltype({1,2}) está mal formada. Que no tenga tipo implica que la deducción de tipo de plantilla no puede deducir un tipo que coincida una lista de inicializadores entre llaves, de tal manera que dada la declaración template<class T> void f(T); la expresión f({1,2,3}) está mal formada. Sin embargo, el parámetro de plantilla puede deducirse, como es el caso de std::vector<int> v(std::istream_iterator<int>(std::cin), {}), donde el tipo del iterador se deduce por el primer argumento pero también se usa en la segunda posición del parámetro. Se hace una excepción especial para deducción de tipos usando la palabra clave auto , que deduce cualquier lista de inicializadores entre llaves como std::initializer_list en la inicialización de lista de copia.
De igual manera, como la lista de inicializadores entre llaves no tiene tipo, se aplican las reglas especiales para la resolución de sobrecarga cuando se usa como un argumento para una llamada a una función sobrecargada.
|
Los agregados se inicializan mediante la inicialización de copia/movimiento directamente a partir de listas de inicializadores entre llaves de un solo elemento del mismo tipo, pero los no agregados primero consideran los constructores que toman una std::initializer_list: struct X {
X() = default;
X(const X&) = default;
};
struct Q {
Q() = default;
Q(Q const&) = default;
Q(std::initializer_list<Q>) {}
};
int main() {
X x;
X x2 = X { x }; // constructor de copia (no es inicialización de agregado)
Q q;
Q q2 = Q { q }; // constructor que toma una lista de inicializadores
// (no es el constructor de copia)
}
|
(desde C++14) |
Ejemplo
#include <iostream>
#include <vector>
#include <map>
#include <string>
struct Foo {
std::vector<int> mem = {1,2,3}; // inicialización de lista de un miembro no estático
std::vector<int> mem2;
Foo() : mem2{-1, -2, -3} {} // inicialización de lista de un miembro en el ctor
};
std::pair<std::string, std::string> f(std::pair<std::string, std::string> p)
{
return {p.second, p.first}; // inicialización de lista en la instrucción return
}
int main()
{
int n0{}; // inicialización de un valor (a cero)
int n1{1}; // inicialización de lista directa
std::string s1{'a', 'b', 'c', 'd'}; // llamada al ctor con lista de inicializadores
std::string s2{s1, 2, 2}; // llamada regular al ctor
std::string s3{0x61, 'a'}; // se prefiere ctor con lista de inicializadores (int, char)
int n2 = {1}; // inicialización de lista de copia
double d = double{1.2}; // inicialización de lista de un temporal,
// luego inicialización de copia
std::map<int, std::string> m = { // inicialización de lista anidada
{1, "a"},
{2, {'a', 'b', 'c'} },
{3, s1}
};
std::cout << f({"hola", "mundo"}).first // inicialización de lista
<< '\n'; // en una llamada a función
const int (&ar)[2] = {1,2}; // vincula referencia lvalue al array temporal
int&& r1 = {1}; // vincula referencia rvalue al int temporal
// int& r2 = {2}; // ERROR: no se puede vincular rvalue a ref lvalue no-const
// int bad{1.0}; // ERROR: conversión de estrechamiento
unsigned char uc1{10}; // de acuerdo
// unsigned char uc2{-1}; // ERROR: conversión de estrechamiento
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 << ' ';
}
Salida:
mundo
0 1 1
abcd cd aa
1 a
2 abc
3 abcd
1 2 3 -1 -2 -3
Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
| ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1467 | C++14 | la inicialización de agregados del mismo tipo y char arrays estaba prohibido |
inicialización del mismo tipo se permite |
| CWG 1467 | C++14 | los constructores de std::initializer_list tenían prioridad sobre los constructores de copia para listas de un solo elemento |
las listas de un solo elemento inicializan directamente |