¿Cuándo se pueden omitir las llaves exteriores en una lista de inicializadores?
Recibí el error C2078 en VC2010 al compilar el siguiente código.
struct A
{
int foo;
double bar;
};
std::array<A, 2> a1 =
// error C2078: too many initializers
{
{0, 0.1},
{2, 3.4}
};
// OK
std::array<double, 2> a2 = {0.1, 2.3};
Descubrí que la sintaxis correcta a1
es
std::array<A, 2> a1 =
{{
{0, 0.1},
{2, 3.4}
}};
La pregunta es: ¿por qué se requieren llaves adicionales a1
pero no a2
?
Actualizar
La pregunta parece no ser específica de std::array. Algunos ejemplos:
struct B
{
int foo[2];
};
// OK
B meow1 = {1,2};
B bark1 = {{1,2}};
struct C
{
struct
{
int a, b;
} foo;
};
// OK
C meow2 = {1,2};
C bark2 = {{1,2}};
struct D
{
struct
{
int a, b;
} foo[2];
};
D meow3 = {{1,2},{3,4}}; // error C2078: too many initializers
D bark3 = {{{1,2},{3,4}}};
Todavía no veo por qué struct D
aparece el error, pero B y C no.
Las llaves adicionales son necesarias porque std::array
es un agregado y POD, a diferencia de otros contenedores de la biblioteca estándar. std::array
no tiene un constructor definido por el usuario. Su primer miembro de datos es una matriz de tamaño N
(que se pasa como argumento de plantilla) y este miembro se inicializa directamente con un inicializador. Las llaves adicionales son necesarias para la matriz interna que se está inicializando directamente.
La situación es la misma que:
//define this aggregate - no user-defined constructor
struct Aarray
{
A data[2]; //data is an internal array
};
¿Cómo inicializarías esto? Si haces esto:
Aarray a1 =
{
{0, 0.1},
{2, 3.4}
};
da un error de compilación :
error: demasiados inicializadores para 'Aarray'
Este es el mismo error que obtienes en el caso de std::array
(si usas GCC).
Entonces lo correcto es usar llaves de la siguiente manera:
Aarray a1 =
{
{ //<--this tells the compiler that initialization of `data` starts
{ //<-- initialization of `data[0]` starts
0, 0.1
}, //<-- initialization of `data[0]` ends
{2, 3.4} //initialization of data[1] starts and ends, as above
} //<--this tells the compiler that initialization of `data` ends
};
que se compila bien . Una vez más, se necesitan llaves adicionales porque estás inicializando la matriz interna .
--
Ahora la pregunta es ¿por qué no se necesitan aparatos ortopédicos adicionales en caso de double
?
Es porque double
no es un agregado, mientras que A
lo es. En otras palabras, std::array<double, 2>
es un agregado de agregado, mientras que std::array<A, 2>
es un agregado de agregado de agregado 1 .
1. Creo que todavía se necesitan llaves adicionales en el caso de double (como este ), para cumplir completamente con el Estándar, pero el código funciona sin ellas. ¡Parece que necesito revisar las especificaciones nuevamente! .
Más sobre brackets y brackets adicionales
Busqué en las especificaciones. Esta sección (§8.5.1/11 de C++11) es interesante y se aplica a este caso:
En una declaración del formulario
T x = { a };
Las llaves se pueden omitir en una lista de inicializadores de la siguiente manera . Si la lista de inicializadores comienza con una llave izquierda, entonces la siguiente lista de cláusulas de inicializadores separadas por comas inicializa los miembros de un subagregado; es erróneo que haya más cláusulas inicializadoras que miembros. Sin embargo, si la lista de inicializadores para un subagregado no comienza con una llave izquierda, entonces sólo se toman suficientes cláusulas de inicializador de la lista para inicializar a los miembros del subagregado; cualquier cláusula inicializadora restante se deja para inicializar el siguiente miembro del agregado del cual es miembro el subagregado actual. [ Ejemplo:
float y[4][3] = {
{ 1, 3, 5 },
{ 2, 4, 6 },
{ 3, 5, 7 },
};
es una inicialización completamente reforzada: 1, 3 y 5 inicializan la primera fila de la matriz
y[0]
, es deciry[0][0]
,y[0][1]
, yy[0][2]
. Del mismo modo, las dos líneas siguientes inicializany[1]
yy[2]
. El inicializador finaliza antes de tiempo y, por lo tanto,y[3]s
los elementos se inicializan como si se inicializaran explícitamente con una expresión del formato float(), es decir, se inicializan con 0.0. En el siguiente ejemplo, se omiten las llaves de la lista de inicializadores; sin embargo, la lista de inicializadores tiene el mismo efecto que la lista de inicializadores completamente reforzada del ejemplo anterior.
float y[4][3] = {
1, 3, 5, 2, 4, 6, 3, 5, 7
};
El inicializador de y comienza con una llave izquierda, pero el de for
y[0]
no, por lo tanto se utilizan tres elementos de la lista. Asimismo, los tres siguientes se toman sucesivamente paray[1]
yy[2]
. —fin del ejemplo]
Según lo que entendí de la cita anterior, puedo decir que debería permitirse lo siguiente:
//OKAY. Braces are completely elided for the inner-aggregate
std::array<A, 2> X =
{
0, 0.1,
2, 3.4
};
//OKAY. Completely-braced initialization
std::array<A, 2> Y =
{{
{0, 0.1},
{2, 3.4}
}};
En el primero, las llaves para el agregado interno están completamente elididas, mientras que el segundo tiene una inicialización completamente apoyada. En su caso (el caso de double
), la inicialización utiliza el primer enfoque (las llaves están completamente elididas para el agregado interno).
Pero esto debería prohibirse:
//ILL-FORMED : neither braces-elided, nor fully-braced
std::array<A, 2> Z =
{
{0, 0.1},
{2, 3.4}
};
No se eliminan llaves ni hay suficientes llaves para una inicialización completamente apoyada. Por tanto, está mal formado.