递归使用时 std::initializer_list 的生命周期
Lifetime of std::initializer_list when used recursively
我正在尝试使用 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 ] ] ] ] ]
}
这是安全的还是未定义的行为?
你所做的应该是安全的。 ISO 标准第 6.7.7 节第 6 段描述了第三个也是最后一个上下文,其中临时变量在与完整表达式末尾不同的点被销毁。脚注 (35) 明确指出这适用于 intializer_list
及其底层临时数组的初始化:
The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following: ...
...(其中,根据 7.2.1,glvalue
是一个表达式,其计算确定对象、bit-field 或函数的身份),然后枚举条件。 (6.9)中特别提到:
A temporary object bound to a reference parameter in a function call (7.6.1.2) persists until the completion of the full-expression containing the call.
正如我所读,这保护了构建对 call_it
的顶部调用的最终参数所需的一切,这正是您想要的。
据我所知程序有未定义的行为。
成员声明 std::initializer_list<wrapped> lst;
要求类型完整,因此将隐式实例化 std::initializer_list<wrapped>
。
此时wrapped
是一个不完整的类型。根据[res.on.functions]/2.5,如果没有声明特定的异常,实例化一个不完整类型的标准库模板作为模板参数是未定义的。
我在 [support.initlist] 中没有看到任何此类异常。
另见活动 LWG issue 2493。
我正在尝试使用 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 ] ] ] ] ]
}
这是安全的还是未定义的行为?
你所做的应该是安全的。 ISO 标准第 6.7.7 节第 6 段描述了第三个也是最后一个上下文,其中临时变量在与完整表达式末尾不同的点被销毁。脚注 (35) 明确指出这适用于 intializer_list
及其底层临时数组的初始化:
The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following: ...
...(其中,根据 7.2.1,glvalue
是一个表达式,其计算确定对象、bit-field 或函数的身份),然后枚举条件。 (6.9)中特别提到:
A temporary object bound to a reference parameter in a function call (7.6.1.2) persists until the completion of the full-expression containing the call.
正如我所读,这保护了构建对 call_it
的顶部调用的最终参数所需的一切,这正是您想要的。
据我所知程序有未定义的行为。
成员声明 std::initializer_list<wrapped> lst;
要求类型完整,因此将隐式实例化 std::initializer_list<wrapped>
。
此时wrapped
是一个不完整的类型。根据[res.on.functions]/2.5,如果没有声明特定的异常,实例化一个不完整类型的标准库模板作为模板参数是未定义的。
我在 [support.initlist] 中没有看到任何此类异常。
另见活动 LWG issue 2493。