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.
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
.
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.