Lifetime of a Std::Initializer_List Return Value

lifetime of a std::initializer_list return value

The wording you refer to in 8.5.4/6 is defective, and was corrected (somewhat) by DR1290. Instead of saying:

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

... the amended standard now says:

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

Therefore the controlling wording for the lifetime of the temporary array is 12.2/5, which says:

The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement

Therefore the noisydt objects are destroyed before the function returns.

Until recently, Clang had a bug that caused it to fail to destroy the underlying array for an initializer_list object in some circumstances. I've fixed that for Clang 3.4; the output for your test case from Clang trunk is:

destroyed
destroyed
destroyed
received
destroyed
destroyed
destroyed
received

... which is correct, per DR1290.

Is it possible to return a std::initializer_list from a function?

Returning initializer_list compiles, but it seldom does the right thing. The list is a temporary value and the initializer_list object points to it. It is a dangling pointer.

In your first example, an optimization is applied because the list is a constant expression. Instead of being written into temporary storage, it is a static global. However, this optimization is not specified in the standard. The return value working, is only a form of undefined behavior.

Lifetime of std::initializer_list when used recursively

As far as I can tell the program has undefined behavior.

The member declaration std::initializer_list<wrapped> lst; requires the type to be complete and hence will implicitly instantiate std::initializer_list<wrapped>.

At this point wrapped is an incomplete type. According to [res.on.functions]/2.5, if no specific exception is stated, instantiating a standard library template with an incomplete type as template argument is undefined.

I don't see any such exception in [support.initlist].

See also active LWG issue 2493.

c++11 is it safe to return an initializer_list value?

An initializer_list behaves like a reference extending lifetime of a temporary (the temporary being the array).

Lifetime extension doesn't apply when returning references, so it doesn't apply here too. The compiler is right, the returned list is always dangling.

What is expected lifetime of std::intializer_list object in C++14?

It's subtle.

A std::initializer_list is backed by an underlying array (produced by the compiler). This array is a like a temporary object, and the std::initializer_list is a sort of reference type that binds to it. So it will extend the temporary array's lifetime so long as the "reference" exist.

In C++14, we do not have guaranteed copy elision. So what should happen is as though std::initializer_list<A>{A()} produced a temporary initializer_list, bound another temporary array to it, and copied the temporary initializer_list to l.

std::initializer_list behaves like a regular reference, as far as lifetime extension is concerned. Only the original reference extends the lifetime, and our original is temporary itself. So the underlying array goes out of existence at the end of the full expression containing the initialization of l. Clang is the correct one.

Direct-initialization ...

std::initializer_list<A> l {A()};

... produces the same output on both compilers.

Meanwhile, your original code behaves the same on GCC and Clang when compiling for C++17.

Returning std::initializer_list in clang

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.

c++11 is it safe to return an initializer_list value?

An initializer_list behaves like a reference extending lifetime of a temporary (the temporary being the array).

Lifetime extension doesn't apply when returning references, so it doesn't apply here too. The compiler is right, the returned list is always dangling.

What is the lifetime of the array underlying a std::initializer_list?


  auto a = wrap({1,2,3});

The lifetime of the initializer_list (and its underlying array) ends at the end of that expression, so a contains a dangling pointer.

That means std::cout<< a[2] has undefined behaviour. Similarly for the code in test, std::cout <<a[1]` is also undefined behaviour.

Can anyone explain to me how debug/release mode influences the lifetime of the initializer_list underlying array ? Or provide some other explanation for this behavior.

It's undefined behaviour.

Anything can happen.

In release mode the compiler optimizes things differently, so the layout of variables on the stack is different, so code with undefined behaviour might do something different. Or it might not.

This isn't even specific to initializer_list, you'd get similar undefined behaviour for any temporary object that you keep pointers into:

wrap( std::vector<int>(1, 1) );

GCC throws init-list-lifetime warning on potentially valid code?


So my understanding is that the common initializer list value, being defined within the scope of an encompassing initializer list, has a lifetime that ends with the enclosing initializer list

You're talking about the object created by the prvalue expression {1, 2, 3}, right?

There's an example in decl.init.list/6,

The array has the same lifetime as any other temporary object ([class.temporary]), except that initializing an initializer_­list object from the array extends the lifetime of the array exactly like binding a reference to a temporary. [Example:

// ...
std::initializer_list<int> i3 = { 1, 2, 3 };
// ...

of which the standard (or draft) says

For i3, the initializer_­list object is a variable, so the array persists for the lifetime of the variable.

This suggests the object must be materialized and should have its lifetime extended.

However, you are not initializing the initializer_list object from the expression, because your variable is already initialized. If we rewrote your code as a call to a notional

module_options.operator=({1, 2, 3})

then we wouldn't expect the temporary lifetime to be extended past the end of the function call.

I had suspected the temporary would still live to the end of the full-expression, since I thought that binding a reference to was supposed to extend its lifetime rather than reduce it: but admittedly class.temporary/6 says "... persists for the lifetime of the reference ..." and not "... persists for at least the lifetime ..."

However, it does mean that the following variation of your original code should do what you want:

std::map<int, std::vector<int>> ex = []{
/* for reused lists */
std::initializer_list<int> module_options { 1, 2, 3 };

return (decltype(ex)) {
{1, module_options},
{2, module_options},
};
}();

Lifetime Extension of a initializer_list return

The library fundamentals TS v2 has std::experimental::make_array, which would certainly satisfy your requirements:

#include <experimental/array>
const auto foo = [](const auto& a, const auto& b, const auto& c) {
return std::experimental::make_array(a, b, c); };

More generally, template argument deduction for constructors would allow you to write:

const auto foo = [](const auto& a, const auto& b, const auto& c) {
return std::vector{a, b, c}; };
^-- no template parameter required

Today, you could emulate this using common_type:

const auto foo = [](const auto& a, const auto& b, const auto& c) {
return std::vector<std::common_type_t<decltype(a), decltype(b), decltype(c)>>{a, b, c}; };


Related Topics



Leave a reply



Submit