List-initialization (since C++11)
Inicializa un objeto desde una lista de inicialización entre llaves .
Contenidos |
Sintaxis
Inicialización directa por lista
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 realiza en las siguientes situaciones:
- inicialización directa de lista (se consideran tanto constructores explícitos como no explícitos)
- inicialización de lista de copia (se consideran tanto los constructores explícitos como los no explícitos, pero solo se pueden llamar 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 está siendo inicializado por lista;
U
's parámetro del constructor es)
Explicación
Los efectos de la inicialización de lista de un objeto de tipo (posiblemente calificado con cv)
T
son:
|
(desde C++20) |
-
Si
Tes una clase agregada y la lista de inicialización entre llaves , que no contiene una lista de inicializadores designados, (desde C++20) tiene una única cláusula de inicialización del mismo tipo o de un tipo derivado (posiblemente cv-calificado), el objeto se inicializa desde esa cláusula de inicialización (mediante copy-initialization para copy-list-initialization, o mediante direct-initialization para direct-list-initialization). -
En caso contrario, si
Tes un array de caracteres y la lista de inicialización entre llaves tiene una única cláusula de inicialización que es un literal de cadena de tipo apropiado, el array se inicializa desde el literal de cadena como es usual .
-
De lo contrario, si
Tes un tipo agregado , se realiza inicialización agregada .
-
De lo contrario, si la lista de inicializadores entre llaves está vacía y
Tes un tipo de clase con un constructor predeterminado, se realiza la value-initialization .
-
De lo contrario, si
Tes una especialización de std::initializer_list , el objeto se inicializa como se describe a continuación .
-
De lo contrario, si
Tes un tipo clase, los constructores deTse consideran, en dos fases:
-
- Todos los constructores que toman std::initializer_list como único argumento, o como primer argumento si los argumentos restantes tienen valores predeterminados, son examinados y emparejados mediante overload resolution contra un único argumento de tipo std::initializer_list .
-
-
Si la etapa anterior no produce una coincidencia, todos los constructores de
Tparticipan en la resolución de sobrecarga contra el conjunto de argumentos que consiste en las cláusulas inicializadoras de la lista de inicialización entre llaves, con la restricción de que solo se permiten 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 (nótese que, en la inicialización de copia simple, los constructores explícitos no se consideran en absoluto).
-
Si la etapa anterior no produce una coincidencia, todos los constructores de
|
(desde C++17) |
-
En caso contrario (si
Tno es un tipo clase), si la lista de inicialización entre llaves tiene solo una cláusula de inicialización y ya sea queTno sea un tipo referencia o sea un tipo referencia cuyo tipo referenciado sea igual o sea una clase base del tipo de la cláusula de inicialización,Tes direct-initialized (en direct-list-initialization) o copy-initialized (en copy-list-initialization), excepto que no se permiten conversiones que estrechen.
-
De lo contrario, si
Tes un tipo de referencia que no es compatible con el tipo de la cláusula inicializadora:
|
(until C++17) |
|
(since C++17) |
-
De lo contrario, si la lista de inicialización entre llaves no tiene cláusulas de inicialización,
Tes value-initialized .
Inicialización de lista std::initializer_list
Un objeto de tipo std:: initializer_list < E > se construye a partir de una lista de inicialización como si el compilador generara y materializara (desde C++17) un prvalue de tipo "array de N const E ", donde N es el número de cláusulas de inicialización en la lista de inicialización; esto se denomina array de respaldo de la lista de inicialización.
Cada elemento del array de respaldo es copy-initialized con la cláusula inicializadora correspondiente de la lista de inicialización, y el objeto std:: initializer_list < E > se construye para referirse a ese array. Se requiere que un constructor o función de conversión seleccionado para la copia sea accessible en el contexto de la lista de inicialización. Si se requiere una conversión narrowing para inicializar cualquiera de los elementos, el programa está mal formado.
El array subyacente tiene la misma duración que cualquier otro objeto temporal , excepto que inicializar un objeto std::initializer_list desde el array subyacente extiende la duración del array exactamente como vincular una referencia a un temporal .
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}}); } // La inicialización anterior se implementará de manera aproximadamente equivalente a continuación, // asumiendo que el compilador puede construir un objeto initializer_list con un par de // punteros, y entendiendo que `__b` no sobrevive a la llamada a `f`. void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; // array de respaldo f(std::initializer_list<double>(__a, __a + 3)); } void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // array de respaldo f(std::initializer_list<double>(__b, __b + 3)); } void r() { const A __c[3] = {A{1}, A{2}, A{3}}; // array de respaldo q(std::initializer_list<A>(__c, __c + 3)); }
Si todos los arrays de respaldo son distintos (es decir, están almacenados en objetos no superpuestos ) no está especificado:
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}); // el resultado no está especificado: // los arrays posteriores pueden compartir // almacenamiento dentro de {1, 2, 3, 4}
Conversiones de estrechamiento
La inicialización de lista limita las conversiones implícitas permitidas implícitas al prohibir lo siguiente:
- conversión de un tipo de punto flotante a un tipo entero
-
conversión desde un tipo de punto flotante
Ta otro tipo de punto flotante cuyo rango de conversión de punto flotante no es mayor ni igual que el deT, excepto cuando el resultado de la conversión es una expresión constante y se satisface una de las siguientes condiciones:- El valor convertido es finito, y la conversión no produce desbordamiento.
- Los valores antes y después de la conversión no son finitos.
- conversión de un tipo entero a un tipo de punto flotante, excepto cuando la fuente es una expresión constante cuyo valor puede almacenarse exactamente en el tipo de destino
-
conversión de tipo entero o tipo de enumeración no delimitada a tipo entero que no puede representar todos los valores del original, excepto cuando
- la fuente es un campo de bits cuyo ancho w es menor que el de su tipo (o, para un tipo de enumeración , su tipo subyacente) y el tipo destino puede representar todos los valores de un tipo entero hipotético extendido con ancho w y con el mismo signo que el tipo original, o
- la fuente es una expresión constante cuyo valor puede almacenarse exactamente en el tipo destino
- conversión de un tipo puntero o tipo puntero-a-miembro a bool
Notas
Cada cláusula de inicialización está secuenciada antes de cualquier cláusula de inicialización que la siga en la lista de inicialización entre llaves. Esto contrasta con los argumentos de una expresión de llamada a función , que están no secuenciados (until C++17) indeterminadamente secuenciados (since C++17) .
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 formado. No tener tipo implica que la deducción de tipos de plantilla no puede deducir un tipo que coincida con una lista de inicializadores entre llaves, así 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 de otra manera, 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 de parámetro. Se hace una excepción especial para la 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 por copia.
También debido a que una lista de inicialización entre llaves no tiene tipo, reglas especiales para la resolución de sobrecarga se aplican cuando se utiliza como argumento para una llamada a función sobrecargada.
Los agregados copian/mueven inicializan directamente desde una lista de inicialización entre llaves de una única cláusula inicializadora del mismo tipo, pero los no-agregados consideran primero los constructores de std::initializer_list :
struct X {}; // agregado struct Q // no agregado { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // constructor de copia (no inicialización de agregado) Q q; Q q2 = Q{q}; // constructor de lista de inicialización (no constructor de copia) }
Algunos compiladores (por ejemplo, gcc 10) solo consideran la conversión de un puntero o un puntero-a-miembro a bool como un estrechamiento en modo C++20.
| Macro de prueba de características | Valor | Std | Característica |
|---|---|---|---|
__cpp_initializer_lists
|
200806L
|
(C++11) | Inicialización de lista y std::initializer_list |
Ejemplo
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // inicialización por lista de un miembro no estático std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // inicialización por lista de un miembro en constructor }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // inicialización por lista en sentencia return } int main() { int n0{}; // inicialización por valor (a cero) int n1{1}; // inicialización directa por lista std::string s1{'a', 'b', 'c', 'd'}; // llamada al constructor de lista de inicialización std::string s2{s1, 2, 2}; // llamada a constructor regular std::string s3{0x61, 'a'}; // se prefiere el ctor de lista de inicialización a (int, char) int n2 = {1}; // inicialización por copia de lista double d = double{1.2}; // inicialización por lista de un prvalue, luego copia-init auto s4 = std::string{"HelloWorld"}; // igual que arriba, no se crea temporal // desde C++17 std::map<int, std::string> m = // inicialización de lista anidada { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // inicialización por lista en llamada a función << '\n'; const int (&ar)[2] = {1, 2}; // vincula una referencia lvalue a un array temporal int&& r1 = {1}; // vincula una referencia rvalue a un int temporal // int& r2 = {2}; // error: no se puede vincular rvalue a una referencia lvalue no constante // int bad{1.0}; // error: conversión que reduce precisión unsigned char uc1{10}; // correcto // unsigned char uc2{-1}; // error: conversión que reduce precisión 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); // tiene efecto de [[maybe_unused]] }
Salida:
world 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 que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1288 | C++11 |
la inicialización de lista de una referencia con una lista de inicialización entre llaves de una
única cláusula de inicialización siempre vinculaba la referencia a un temporal |
vincular a esa cláusula de inicialización
si es válido |
| CWG 1290 | C++11 | el tiempo de vida del array de respaldo no estaba especificado correctamente |
especificado igual que otros
objetos temporales |
| CWG 1324 | C++11 |
la inicialización consideraba primero la inicialización desde
{}
|
la inicialización de agregado
considerada primero |
| CWG 1418 | C++11 | al tipo del array de respaldo le faltaba const | const añadido |
| CWG 1467 | C++11 |
se prohibía la inicialización del mismo tipo para agregados y arrays de
caracteres; los constructores de lista de inicialización tenían prioridad sobre los constructores de copia para listas de una sola cláusula |
inicialización del mismo tipo
permitida; listas de una sola cláusula inicializan directamente |
| CWG 1494 | C++11 |
al inicializar por lista una referencia con una cláusula de inicialización de un
tipo incompatible, no se especificaba si el temporal creado era inicializado por lista directa o por lista de copia |
depende del
tipo de inicialización para la referencia |
| CWG 2137 | C++11 |
los constructores de lista de inicialización perdían frente a los constructores
de copia al inicializar por lista
X
desde
{X}
|
los no-agregados consideran
las listas de inicialización primero |
| CWG 2252 | C++17 | las enumeraciones podían ser inicializadas por lista desde valores no escalares | prohibido |
| CWG 2267 | C++11 |
la resolución de
CWG issue 1494
aclaró
que los temporales podían ser inicializados por lista directa |
son inicializados por lista de copia
al inicializar por lista referencias |
| CWG 2374 | C++17 | la inicialización por lista directa de un enum permitía demasiados tipos de origen | restringido |
| CWG 2627 | C++11 |
un campo de bits estrecho de un tipo entero mayor puede promocionarse a
un tipo entero menor, pero seguía siendo una conversión de estrechamiento |
no es una
conversión de estrechamiento |
| CWG 2713 | C++20 |
las referencias a clases agregado no podían
ser inicializadas por listas de inicializadores designados |
permitido |
| CWG 2830 | C++11 | la inicialización por lista no ignoraba la calificación cv de nivel superior | ignora |
| CWG 2864 | C++11 | las conversiones de punto flotante que desbordaban no eran de estrechamiento | son de estrechamiento |
| P1957R2 | C++11 |
la conversión desde un puntero/puntero-a-miembro
a bool no era de estrechamiento |
considerada de estrechamiento |
| P2752R3 | C++11 | los arrays de respaldo con tiempo de vida superpuesto no podían superponerse | pueden superponerse |