c++ c++14 initializer-list

¿Por qué la inicialización de la matriz de pares todavía necesita llaves dobles en C++ 14?



c++14 initializer-list (5)

En teoría, std::array debe inicializarse con inicialización agregada. Así que en realidad esto:

std::array<int, 3> a {1, 2, 3};

Es un azúcar sintáctico para esto:

std::array<int, 3> a {{1, 2, 3}};

Como puede ver, en el primero parece que inicializo la matriz con valores, pero en realidad es una inicialización agregada con una lista de iniciación. Esto queda claro como un día en la segunda situación. Así que eso es para empezar.

Ok, ¿por qué no funciona esto?

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

Bueno, en pocas palabras, el compilador no puede distinguir qué tipo de sintaxis está utilizando para inicializar la matriz. {1, 11} puede interpretarse como lista de inicialización y usar la primera versión o puede interpretarse como un par e ir con la segunda versión.

Este código:

std::array<std::pair<int, int>, 3> b {{{1, 11}, {2, 22}, {3, 33}}};.

elimina la ambigüedad.

Fuente: http://en.cppreference.com/w/cpp/language/aggregate_initialization

Con el estándar C ++ 14, la inicialización de un std::array puede ir con llaves simples (consulte http://en.cppreference.com/w/cpp/container/array ):

Esto, sin embargo, no funciona para un std::array of std::pair .

¿Por qué funcionan estos:

std::pair<int, int> p { 1, 2 }; std::array<int, 3> a {1, 2, 3};

pero esto no funciona:

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

mientras que esto funciona de nuevo?

std::array<std::pair<int, int>, 3> b {{{1, 11}, {2, 22}, {3, 33}}};

Además, para completar, la inicialización de una buena matriz antigua funciona con llaves individuales

std::pair<int, int> c[3] {{1, 11}, {2, 22}, {3, 33}};


Esto parece ser una ambuguidad de análisis algo similar al famoso y desconcertante análisis . Sospecho que lo que está pasando es que:

Si tú escribes

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

El compilador tiene dos formas de interpretar la sintaxis:

  1. Realiza una inicialización de refuerzo completo (lo que significa que el soporte externo se refiere a la inicialización agregada de la std::array , mientras que la primera más interna inicializa la representación interna de miembro de la std::array que es una matriz C real). Esto no podrá compilarse, ya que posteriormente std::pair<int, int> no se puede inicializar con 1 (todas las llaves están agotadas). clang dará un error de compilación que indica exactamente que:

    error: no viable conversion from ''int'' to ''std::pair<int, int>'' std::array<std::pair<int, int>, 3> a{{1, 11}, {2, 22}, {3, 33}}; ^

    Tenga en cuenta también que este problema se resuelve si no hay un agregado interno de miembros que deba inicializarse, es decir,

    std::pair<int, int> b[3] = {{1, 11}, {2, 22}, {3, 33}};

    Se compilará bien como inicialización agregada.

  2. (De la forma en que lo dijiste). Realizas una inicialización con llaves, las llaves más internas, por lo tanto, son para la inicialización agregada de los pares individuales, mientras que las llaves para las representaciones de la matriz interna se eliminan. Tenga en cuenta que incluso si no existiera esta ambigüedad, como se señaló correctamente en la respuesta de rustyx , las reglas de la orientación no se aplican, ya que std::pair no es un tipo agregado, por lo que el programa aún estaría mal formado.

El compilador preferirá la opción 1. Al proporcionar las llaves adicionales, realiza la inicialización completa de la llave y levanta cualquier ambigüedad sintáctica.


La regla de elision de refuerzo de C ++ 14 se aplica solo a la inicialización de subagregados.

Así, por ejemplo, algo como esto funciona:

std::array<std::array<int, 3>, 3> a{1, 11, 2, 22, 3, 33};

Aquí se puede inicializar una lista de agregados sin llaves adicionales.

Pero std::pair no es un aggregate (tiene constructores), por lo que la regla no se aplica.

Lo que significa que sin la regla de std::array de llaves, std::array , que es un conjunto con una matriz en su interior, necesita un conjunto extra de llaves para poder iniciar la lista . Recuerde que la array plantillas de clase se implementa como:

template<typename T, std::size_t N> struct array { T elems[N]; };

Para listarlo-inicializarlo sin la regla de elems de llaves, necesita un conjunto adicional de llaves para llegar al miembro elems .


Sin las llaves dobles, la afirmación es simplemente ambigua. Considere el siguiente código:

std::array<std::pair<int, int>, 1> a = {{ {1, 2} }}; std::array<int, 2> b = { {1, 2} };

Sin llaves dobles en la primera definición, el compilador tratará a { {1,2} } como una lista de inicialización escalar para la array<int, 2> . Debe declarar una lista -iniciada anidada explícita para que el compilador reconozca que la lista interna también está inicializada de forma agregada (frente a la escalable inicializada), de modo que pueda construir una matriz de std::pair .


Voy a adivinar aquí.
La lista de inicializadores para std::array<T,n> debe ser una lista de T (o trivialmente construible para T ). Para que pudieras hacer

std::array<std::pair<int,int>,3> b { std::pair{1,11}, std::pair{2,22}, std::pair{3,33} };

Pero eso es tediosamente verboso. Para obtener la conversión a std::pair<int,int> que desea, debe proporcionar una lista de inicialización, por lo que

std::array<std::pair<int,int>,3> b { { // element 1 { // initialize from: { 1,11 } // std::initializer_list } }, ... };

No puedo seguir defendiendo esto, pero tenga en cuenta que std::vector<T, Allocator>::vector( std::initializer_list<T>, const Allocator& alloc=Allocator()) está definido pero std::array<T,n>::array( std::initializer_list<T> ) no lo es. Tampoco está definido std::pair<U,T>::pair( std::initializer_list<??> ) .