Lista de inicializadores dentro de std :: pair

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, mas retorna segfault. Por quê?

Testado no gcc 8.3.0 e em compiladores online.

rin
fonte
11
Por conveniência: os links Godbolt com e sem std::pair .
precisa

Respostas:

24

std::initializer_listnão é para ser armazenado, é apenas para ... bem a inicialização. Internamente, apenas armazena um ponteiro para o primeiro elemento e o tamanho. No seu código, os std::stringobjetos são temporários e os initializer_listdois não se apropriam deles, nem prolongam a vida deles, nem os copiam (porque não é um contêiner), para que saiam do escopo imediatamente após a criação, mas você initializer_listainda mantém um ponteiro para eles. É por isso que você recebe uma falha de segmentação.

Para armazenar, você deve usar um contêiner, como std::vectorou std::array.

Bolov
fonte
Incomoda-me que isso seja compilável. Linguagem boba :(
Raças da leveza na órbita
11
@LightnessRaceswithMonica Eu tenho muita carne com initializer_list. Não é possível usar objetos somente para movimentação, portanto, não é possível usar o init da lista com o vetor unique_ptr, por exemplo. O tamanho de initializer_listnão é uma constante em tempo de compilação. E o fato de que std::vector<int>(3)e std::vector<int>{3}fazer coisas completamente diferentes. Me deixa triste :(
bolov 04/04/19
Sim mesmo ... :(
Lightness Races in Orbit
3

Gostaria apenas de adicionar um pouco mais de detalhes. Uma matriz subjacente de std::initializer_listcomportamentos se comporta da mesma forma que os temporários. Considere a seguinte classe:

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

e seu uso no seguinte código:

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

Imprime

ctor
dtor
barrier

desde que na primeira linha, uma instância temporária do tipo Xé criada (convertendo o construtor de 1) e destruída também. A referência armazenada pé então pendente.

Quanto a std::initializer_list, se você usá-lo desta maneira:

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

então, a matriz subjacente (temporária) existe enquanto lsair. Portanto, a saída é:

ctor
ctor
barrier
dtor
dtor

No entanto, se você mudar para

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

A saída é novamente

ctor
dtor
barrier

já que a matriz (temporária) subjacente existe apenas na primeira linha. A desreferenciação do ponteiro para os elementos do lresultado resulta em um comportamento indefinido.

A demonstração ao vivo está aqui .

Daniel Langr
fonte