3

When using std::initializer_list I have experienced some difficulties. It didn't take long to realise that I thought of it more as a container, when in fact it has reference semantics. So my question is, which of the following examples might cause problems and if not, why do they work? I should add that I'm using VS2013 and std::initializer_list is implemented using only a begin and end pointer.

Example 1

const auto tmpList = {1, 2, 3};
const vector<int> test(tmpList);

This may work if the literals 1, 2 and 3 are stored in a contiguous block of memory. But is this guaranteed?

Example 2

const string a("foo");
int someOtherVariable = 10;
const string b("bar");
const auto tmpList = {a, b};
const vector<string> test(tmpList);

This should not work, since a and b might be in different places on the stack (remember std::initializer_list simply keeps a pointer to the first string). But then again, the compiler should be able to handle this somehow, since this should work in my understanding:

const vector<string> test({a, b});

Or does it?

Example 3

const auto tmpList = {string("foo"), string("bar")};
const vector<string> test(tmpList);

In my point of view the initializer list points to already destroyed temporaries when passed to the vector.

Conclusions

I think all these examples show that std::initializer_list should not be used as a temporary container. If that is the case, shouldn't storing an initializer list anywhere (except as parameter to a function) be prohibited? Maybe I'm also just missing on some compiler magic which sees to it that the pointers always point to valid, contiguous memory.

Solution & Background

It seems all of the above examples are well defined. There seems to be a bug either in my program or in the VS2013 C++ compiler. The problem arose first when I used an initializer list like this:

const auto tmpList = {join(myList1), join(myList2)};
const vector<string> test(tmpList);

join is a function which returns a std::string. In this case the initializer list contained 2 entries, but the first one was empty. Splitting it up into this resolves the problem:

const auto str1 = join(myList1);
const auto str2 = join(myList2);
const auto tmpList = {str1, str2};
const vector<string> test(tmpList);

Now that I think of it, it looks like a compiler bug to me, but it led me to believe that the initializer list actually stored pointers directly to the literals, stack variables and so on instead of first copying them to a local array.

Excelcius
  • 1,680
  • 1
  • 14
  • 31
  • All three cases should work fine (but don't take my word for it, read the specification!). – Some programmer dude Feb 19 '14 at 15:12
  • Because you're copying the values into an vector, it should be fine. But supposedly using an `initializer_list` as a container is a bad idea. –  Feb 19 '14 at 15:14
  • @JoachimPileborg Well we have seen that it didn't work, especially example 3. Which makes sense since the list simply stores begin and end pointer and temporaries will no longer exist after the assignment (except if there is some special compiler treatment for initializer lists). – Excelcius Feb 19 '14 at 15:14
  • @Excelcius: Have you really seen that it doesn't work? In that case, your compiler is buggy and isn't giving the array the same lifetime as the `initializer_list`. – Mike Seymour Feb 19 '14 at 15:23
  • It doesn't work in my specific case, I can reproduce the problem. But indeed, the above examples all work as expected and now I know why. I have added an example where it didn't work, but it looks like a compiler bug. – Excelcius Feb 19 '14 at 15:41
  • 1
    I also ran into compiler problems with the initializer_list: http://stackoverflow.com/questions/20165166/double-delete-in-initializer-list-vs-2013 – Jasper Feb 19 '14 at 15:48
  • @Jasper Thanks for the info - i have debugged the initializer list and noticed 3 string deletes in the example above when there should be just 2. It seems it's exactly your problem, the first item is deleted before being used and I'm just lucky that it doesn't crash. – Excelcius Feb 19 '14 at 15:59

2 Answers2

4

C++11 8.5.4/6 The lifetime of the array is the same as that of the initializer_list object.

So there are no lifetime issues in any of your examples. You'd need to do something convoluted like

std::initializer_list<int> bad;
{
    bad = {1,2,3};
}
std::vector<int> test(bad);  // Boom!

to encounter problems.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
4

All your examples have well defined behavior. From §8.5.4/5

An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. ... [ Example:

 struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

The initialization will be implemented in a way roughly equivalent to this:

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

...—end example ]

Also, §8.5.4/6 is relevant

The array has the same lifetime as any other temporary object (12.2), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary.

The standard even gives examples of valid and invalid code after that. The invalid code sample is this:

struct A {
    std::initializer_list<int> i4;
    A() : i4{ 1, 2, 3 } {} // creates an A with a dangling reference
};

It's invalid because the lifetime of the temporary array created for the initializer list ends when the constructor body ends, leaving i4 with dangling references.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Thanks I have chosen this as the answer since it explains a detail I haven't realized until now: There is a hidden array created when using an initializer list. My problems seem to be compiler related, see my updated question. – Excelcius Feb 19 '14 at 15:33
  • 2
    @Excelcius That is, indeed, a compiler bug. See [this](https://stackoverflow.com/q/5719636/241631) question, and the corresponding [bug report](https://connect.microsoft.com/VisualStudio/feedback/details/674849) on connect. And [another](https://stackoverflow.com/questions/20165166/double-delete-in-initializer-list-vs-2013) SO about this behavior. – Praetorian Feb 19 '14 at 16:15