GCC对可能有效的代码抛出init list生存期警告



我在不稳定的Debian上运行GCC 9.3.0。

最近我工作的一个项目发生了变化,引入了与下面类似的代码。

#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:

原始初始值设定项列表对象的生存期结束后,不保证基础数组存在。std::initializer_list的存储未指定(即,根据情况,它可以是自动、临时或静态只读存储器(。

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

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

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

您说的是由prvalue表达式{1, 2, 3}创建的对象,对吗?

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

数组与任何其他临时对象([class.temporary](具有相同的生存期,不同之处在于从数组初始化initializer_­list对象会延长数组的生存期——就像将引用绑定到临时对象一样。示例:

// ...
std::initializer_list<int> i3 = { 1, 2, 3 };
// ...

其中标准(或草案(规定

对于i3initializer_­list对象是一个变量,因此数组在变量的生存期内保持不变。

这表明对象必须物化,并且应该延长其生存期。

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

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

最新更新