Lista de inicializadores dentro de std :: par

26

Este código:

#include <iostream>
#include <string>

std::pair<std::initializer_list<std::string>, int> groups{ { "A", "B" }, 0 };

int main()
{
    for (const auto& i : groups.first)
    {
        std::cout << i << '\n';
    }
    return 0;
}

compila pero devuelve segfault. ¿Por qué?

Probado en gcc 8.3.0 y en compiladores en línea.

rin
fuente
1
Para mayor comodidad: Godbolt se vincula con y sin std::pair .
Max Langhof

Respuestas:

24

std::initializer_listno está destinado a ser almacenado, solo está destinado a ... bien la inicialización. Internamente solo almacena un puntero al primer elemento y el tamaño. En su código, los std::stringobjetos son temporales y initializer_listninguno de ellos toma posesión de ellos, ni extiende su vida, ni los copia (porque no es un contenedor), por lo que quedan fuera del alcance inmediatamente después de la creación, pero initializer_listaún tiene un puntero hacia ellos. Es por eso que tienes una falla de segmentación.

Para almacenar debe usar un contenedor, como std::vectoro std::array.

bolov
fuente
Me molesta que esto sea compilable. Lenguaje tonto :(
ligereza corre en órbita el
1
@LightnessRaceswithMonica Tengo mucha carne con initializer_list. No es posible usar objetos de solo movimiento, por lo que no puede usar list init con el vector de unique_ptr por ejemplo. El tamaño de initializer_listno es una constante de tiempo de compilación. Y el hecho de que std::vector<int>(3)y std::vector<int>{3}hacer cosas completamente diferentes. Me
pone
Sí, lo mismo ... :(
ligereza corre en órbita el
3

Solo agregaría un poco más de detalles. Una serie subyacente de std::initializer_listcomportamientos de forma similar a los temporales. Considere la siguiente clase:

struct X
{
   X(int i) { std::cerr << "ctor\n"; }
   ~X() { std::cerr << "dtor\n"; }
};

y su uso en el siguiente código:

std::pair<const X&, int> p(1, 2);
std::cerr << "barrier\n";

Se imprime

ctor
dtor
barrier

dado que en la primera línea, Xse crea una instancia temporal de tipo (al convertir el constructor de 1) y también se destruye. La referencia almacenada pentonces está colgando.

En cuanto a std::initializer_list, si lo usa de esta manera:

{
   std::initializer_list<X> l { 1, 2 };
   std::cerr << "barrier\n";
}

entonces, la matriz subyacente (temporal) existe mientras exista l. Por lo tanto, la salida es:

ctor
ctor
barrier
dtor
dtor

Sin embargo, si cambia a

std::pair<std::initializer_list<X>, int> l { {1}, 2 };
std::cerr << "barrier\n";

La salida es de nuevo

ctor
dtor
barrier

ya que la matriz subyacente (temporal) existe solo en la primera línea. Desreferenciar el puntero a los elementos de lentonces resulta en un comportamiento indefinido.

La demostración en vivo está aquí .

Daniel Langr
fuente