Inicialización de lista (desde C++11)
Inicializa un objeto a partir de una lista de inicializadores entre llaves.
Contenido |
[editar] Sintaxis
[editar] 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) | ||||||||
[editar] 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):
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
);[editar] 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
T
es una especialización de std::initializer_list, el objetoT
se 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
T
en 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
T
participan 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
T
no es un tipo clase), si la lista de inicializadores entre llaves solamente tiene un elemento y ya sea queT
no 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,T
se 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
T
es 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,
T
se inicializa mediante la inicialización de un valor.
[editar] 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 double a double o a float y la conversión de double a float, 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) |
[editar] 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) |
[editar] 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
[editar] 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 |