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

ktb*_*ktb 6 c++ gcc language-lawyer c++11

我在带有 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},
    };
}();
Run Code Online (Sandbox Code Playgroud)

这个想法是初始化列表的相同子部分首先在顶部声明,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 |         }},
      |         ^
Run Code Online (Sandbox Code Playgroud)

根据cppreference

在原始初始化列表对象的生命周期结束后,不能保证底层数组存在。std::initializer_list 的存储是未指定的(即它可以是自动、临时或静态只读内存,取决于情况)。

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

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

Use*_*ess 2

\n

所以我的理解是,公共初始值设定项列表值在包含初始值设定项列表的范围内定义,其生命周期以封闭初始值设定项列表结束

\n
\n

您正在谈论由纯右值表达式创建的对象{1, 2, 3},对吧?

\n

decl.init.list/6中有一个示例,

\n
\n

该数组与任何其他临时对象 ([ class.temporary ]) 具有相同的生命周期,只不过initializer_\xc2\xadlist从数组初始化对象会延长数组的生命周期,就像将引用绑定到临时对象一样。[例子:

\n
\n
// ...\nstd::initializer_list<int> i3 = { 1, 2, 3 };\n// ...\n
Run Code Online (Sandbox Code Playgroud)\n

其中标准(或草案)说

\n
\n

对于i3initializer_\xc2\xadlist对象是一个变量,因此数组在变量的生命周期内持续存在。

\n
\n

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

\n

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

\n
module_options.operator=({1, 2, 3})\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n
std::map<int, std::vector<int>> ex = []{\n    /* for reused lists */\n    std::initializer_list<int> module_options { 1, 2, 3 };\n\n    return (decltype(ex)) {\n        {1, module_options},\n        {2, module_options},\n    };\n}();\n
Run Code Online (Sandbox Code Playgroud)\n