26

This code:

#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;
}

compiles but returns segfault. Why?

Tested on gcc 8.3.0 and on online compilers.

rin
  • 295
  • 2
  • 6
  • 1
    For convenience: Godbolt links [with](https://godbolt.org/z/vWp6Ey) and [without](https://godbolt.org/z/B6x-Q_) `std::pair`. – Max Langhof Dec 02 '19 at 10:28

2 Answers2

24

std::initializer_list is not meant to be stored, it is just meant for ... well initialization. Internally it just stores a pointer to the first element and the size. In your code the std::string objects are temporaries and the initializer_list neither takes ownership of them, neither extends their life, neither copies them (because it's not a container) so they go out of scope immediately after creation, but your initializer_list still holds a pointer to them. That is why you get segmentation fault.

For storing you should use a container, like std::vector or std::array.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • It bothers me that this is compileable. Silly language :( – Lightness Races in Orbit Dec 04 '19 at 11:56
  • 1
    @LightnessRaceswithMonica I have a lot of beef with `initializer_list`. It is not possible to use move-only objects, so you cannot use list init with vector of unique_ptr for instance. The size of `initializer_list` is not a compile-time constant. And the fact that `std::vector(3)` and `std::vector{3}` do completely different things. Makes me sad :( – bolov Dec 04 '19 at 16:23
3

I would just add a bit more details. An underlying array of std::initializer_list behaves kind-of similarly as temporaries. Consider the following class:

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

and its usage in the following code:

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

It prints out

ctor
dtor
barrier

since at the first line, a temporary instance of type X is created (by converting constructor from 1) and destroyed as well. The reference stored into p is then dangling.

As for std::initializer_list, if you use it this way:

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

then, the underlying (temporary) array exist as long as l exits. Therefore, the output is:

ctor
ctor
barrier
dtor
dtor

However, if you switch to

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

The output is again

ctor
dtor
barrier

since the underlying (temporary) array exists only at the first line. Dereferencing the pointer to the elements of l then results in undefined behavior.

Live demo is here.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93