std::initializer_list递归使用时的生存期



我试图使用std::initializer_list来定义和输出递归数据结构。在下面的例子中,我正在处理一个列表,其中每个元素可以是整数,也可以是同一类型列表的另一个实例。我使用中间变体类型来实现这一点,它可以是初始化列表,也可以是整数。

我不清楚的是std::initializer_list的生命周期是否足够长以支持这个用例,或者我是否会遇到内存访问不一致的可能性。代码运行良好,但我担心不能保证这一点。我希望std::initializer_list和用于创建最顶层列表的任何中间、临时std::initializer_list对象仅在顶级表达式完成后才被清理。

struct wrapped {
bool has_list;
int n = 0;
std::initializer_list<wrapped> lst;
wrapped(int n_) : has_list(false), n(n_) {}
wrapped(std::initializer_list<wrapped> lst_) : has_list(true), lst(lst_) {}
void output() const {
if (!has_list) {
std::cout << n << ' ';
} else {
std::cout << "[ ";
for (auto&& x : lst)  x.output();
std::cout << "] ";
}
}
};
void call_it(wrapped w) {
w.output();
std::cout << std::endl;
}
void call_it() {
call_it({1});                 // [ 1 ]
call_it({1,2, {3,4}, 5});     // [ 1 2 [ 3 4 ] 5 ]
call_it({1,{{{{2}}}}});       // [ 1 [ [ [ [ 2 ] ] ] ] ]
}

这是安全的还是未定义的行为?

据我所知,这个程序有未定义的行为。

成员声明std::initializer_list<wrapped> lst;要求类型是完整的,因此将隐式实例化std::initializer_list<wrapped>

此时,wrapped是一个不完全类型。根据[res.on.functions]/2.5,如果没有指定异常,将不完整类型作为模板实参的标准库模板实例化是未定义的。

我在[support.initlist]中没有看到任何这样的异常。

参见active LWG issue 2493。

你所做的应该是安全的。ISO标准第6.7.7节第6段描述了第三个也是最后一个上下文,在这个上下文中,临时变量在与完整表达式末尾不同的地方被销毁。脚注(35)明确指出,这适用于intializer_list及其底层临时数组的初始化:

如果引用绑定到的glvalue是通过下列方式之一获得的,则引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在:…

…(根据7.2.1,glvalue是一个表达式,其求值确定对象、位域或函数的身份),然后枚举条件。在(6.9)中,它特别提到:

在函数调用(7.6.1.2)中绑定到引用形参的临时对象一直存在,直到包含该调用的完整表达式完成。

当我阅读它时,这保护了构建call_it的top调用的最终实参所需的一切,这正是您想要的。

最新更新