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 ofN
elements of typeE
, whereN
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 thestd::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
, theinitializer_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
Why Does the Free() Function Not Return Memory to the Operating System
How to Read Linux File Permission Programmatically in C/C++
Cannot Get Makefile to Build Each Object from Its Corresponding Source
How to Write Log Base(2) in C/C++
Read Unicode Utf-8 File into Wstring
Is It Worth Setting Pointers to Null in a Destructor
Extending the C++ Standard Library by Inheritance
Why Istream Object Can Be Used as a Bool Expression
Floating Point Equality and Tolerances
Why Is a NaïVe C++ Matrix Multiplication 100 Times Slower Than Blas
Colour Output of Program Run Under Bash
How to Correctly Interpose Malloc Allowing for Ld_Preload Chaining
What Is the Easiest Way to Parse an Ini File in C++
How Does the Omp Ordered Clause Work
Why Doesn't Sfinae (Enable_If) Work for Member Functions of a Class Template
Simple Illumination Correction in Images Opencv C++
Error Lnk2019: Unresolved External Symbol _Main Referenced in Function _Tmaincrtstartup