10

Consider this sample of code:

#include <initializer_list>
#include <iostream>

int main()
{
    for(auto e: []()->std::initializer_list<int>{return{1,2,3};}())
        std::cout<<e<<std::endl;
    return 0;
}

I tried to compile it with g++ (gcc version 4.9.2 (Debian 4.9.2-10)) and the output is correct. In clang++ (Debian clang version 3.5.0-9 (tags/RELEASE_350/final) (based on LLVM 3.5.0)) output for example:

0
2125673120
32546

Where first line are always 0 and last two are "random".

It's error in clang or something else? I think that this sample of code is correct.

Update:

When the lambda function return type is something else (e.g. std::vector or std::array) this code works fine.

  • accepting the duplicate recommendation: the code here is functionally identical to the example at the bottom of the linked question (range-based for loop binds as `auto && __begin = `) – M.M Feb 28 '15 at 00:09

2 Answers2

4

From C++11 8.5.4 List Initialization [dcl.init.list]:

5 An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type 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. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.

6 The lifetime of the array is the same as that of the initializer_list object.

The return statement of your lambda initializes a temporary std::initializer_list<int> and returns a copy thereof. This is all good, except that the lifetime of the array to which it refers ends at the end of the full-expression. Accessing the dead array through the initializer_list outside of the lambda results in undefined behavior.

An initializer_list isn't a container, it's a reference to a temporary container. If you try to use it like a container you're going to have a bad time.

In C++14 (quoting N4140) paragraph 6 was clarified to:

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

by the resolution of CWG issue 1290. This clarification makes it impossible to use an initializer_list as, e.g., a member variable which was the intention of C++11. Even in C++14, however, your program has undefined behavior.

Community
  • 1
  • 1
Casey
  • 41,449
  • 7
  • 95
  • 125
  • ... but the specification of range-based for loops defines the `initializer_list` object to be assigned to a variable with (deduced) rvalue reference type: `auto && __range = range-init;` and the temporaries lifetime is thus extended, isn't it? No copy of the temporary is returned, the temporary itself is returned: It is the temporary return value of that lambda that the reference is bound to. Or am I missing something? – Columbo Feb 27 '15 at 22:24
  • @Columbo The underlying array should be destroyed before the lambda returns. –  Feb 27 '15 at 22:53
1

In C++11, the underlying array is not guaranteed to exist after the lifetime of the original initialiser list object has ended. Therefore, your code may exhibit undefined behaviour. Switch to C++14.