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.

有帮助吗?

解决方案

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.

其他提示

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.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top