GCC 对可能有效的代码抛出 init-list-lifetime 警告?

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

我 运行 使用 GCC 9.3.0 不稳定的 Debian。

我从事的项目最近发生了变化,引入了类似于下面的代码。

#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},
    };
}();

这个想法是初始化列表的相同子部分首先在顶部声明,定义并在第一次使用时分配给 std:initializer_list 变量,然后在多个地方使用。这很方便,有些人可能会争辩说更具可读性,这就是它被接受的原因。

一切都很好,直到几天前 GCC 开始对代码发出 init-list-lifetime 警告。我们在回归中使用 -Werror,所以这对我来说回归失败了。我还尝试使用 clang 9.0.1 进行编译,它不会引发警告。

<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 |         }},
      |         ^

根据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).

所以我的理解是,在包含初始化列表的范围内定义的公共初始化列表值的生命周期以包含的初始化列表结束。在前面的 cppreference 页面中,它提到 std::initializer_list 是一个“轻量级代理对象”,这意味着它不会取得临时对象的所有权或延长其生命周期。这意味着底层数组不能保证在以后的使用中存在,这就是引发警告的原因。这个分析正确吗?

我可以通过将 std::initializer_list 变量初始化移动到声明中来防止出现警告。有关项目中问题的完整详细信息,请参阅 PR.

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

你说的是纯右值表达式 {1, 2, 3} 创建的对象,对吧?

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 };
// ...

其中标准(或草案)说

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

这表明对象必须具体化并且应该延长其生命周期。

但是,您没有从表达式中初始化 initializer_list 对象,因为您的变量已经初始化。如果我们将您的代码重写为对 notional

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

那么我们就不会期望临时生命周期会延长到函数调用结束之后。

我曾怀疑临时对象仍会存活到完整表达式的末尾,因为我认为绑定一个引用应该延长它的生命周期而不是减少它: 但诚然 class.temporary/6"...在引用的整个生命周期中持续存在..." 而不是 "...持续 至少一生..."

但是,这确实意味着您的原始代码的以下变体应该可以满足您的要求:

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},
    };
}();