Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 644
ktb Avatar asked Jul 13 '20 13:07

ktb


People also ask

How do I eliminate the warning-wparentheses in GCC for IF statements?

When there is the potential for this confusion, GCC issues a warning when this flag is specified. To eliminate the warning, add explicit braces around the innermost if statement so there is no way the else can belong to the enclosing if. The resulting code looks like this: This warning is enabled by -Wparentheses .

How to suppress the warning that X is always initialized in GCC?

If the value of y is always 1, 2 or 3, then x is always initialized, but GCC doesn’t know this. To suppress the warning, you need to provide a default case with assert(0) or similar code. This option also warns when a non-volatile automatic variable might be changed by a call to longjmp. The compiler sees only the calls to setjmp.

Why does GCC issue warnings when I use a common variable?

To help detect accidental misuses of such arrays GCC issues warnings unless it can prove that the use is safe. See Common Variable Attributes . Warn for cases where adding an attribute may be beneficial.

Why does GCC not keep track of variables state?

These warnings are only possible in optimizing compilation, because otherwise GCC does not keep track of the state of variables. These warnings are made optional because GCC may not be able to determine when the code is correct in spite of appearing to have an error. Here is one example of how this can happen:


1 Answers

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},
    };
}();
like image 190
Useless Avatar answered Oct 31 '22 14:10

Useless