问题
I'm running on Debian unstable with GCC 9.3.0.
There was a recent change on a project I work on that introduced code similar to what's below.
#include <initializer_list>
#include <map>
#include <vector>
std::map<int, std::vector<int>> ex = []{
/* for reused lists */
std::initializer_list<int> module_options;
return (decltype(ex)) {
{1, module_options = {
1, 2, 3
}},
{2, module_options},
};
}();
The idea is that identical subsections of the initializer lists are first declared at the top, defined and assigned to the std:initializer_list
variable at the first usage, then used in multiple places. This is convenient, and some may argue more readable, which is why it was accepted.
All was well until a few days ago where GCC started throwing a init-list-lifetime
warning on the code. We use -Werror
in our regression, so this fails the regression for me. I also tried compiling with clang 9.0.1, which does not throw the warning.
<source>: In lambda function:
<source>:12:9: warning: assignment from temporary 'initializer_list' does not extend the lifetime of the underlying array [-Winit-list-lifetime]
12 | }},
| ^
According to cppreference:
The underlying array is not guaranteed to exist after the lifetime of the original initializer list object has ended. The storage for std::initializer_list is unspecified (i.e. it could be automatic, temporary, or static read-only memory, depending on the situation).
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. From the cppreference page earlier, it mentions that std::initializer_list
is a "lightweight proxy-object", which implies that it does not take ownership of the temporary object or extend it's lifetime. This means that the underlying array is not guaranteed to exist in later usage, which is why the warning is being thrown. Is this analysis correct?
I can prevent the warning from occuring by moving the std::initializer_list
variable initialization to the declaration. For full details on the problem as it stands in the project see the PR.
回答1:
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},
};
}();
来源:https://stackoverflow.com/questions/62877374/gcc-throws-init-list-lifetime-warning-on-potentially-valid-code