为何初始化列表prvalue传入范围适配器闭包对象无法编译?
核心原因:生存期与悬空引用风险
先看能正常编译的代码:
auto init_list = {1,2,4}; auto v = init_list | views::drop(1);
这里init_list是左值类型的std::initializer_list<int>,它的底层数组生存期和变量init_list完全一致。views::drop返回的视图只是引用这个数组,不会持有数据,因此不存在悬空引用问题,编译器允许这种用法。
而prvalue形式的initializer_list<int>{1,2,4}则完全不同:它的底层数组生存期仅维持到完整表达式结束时就会被销毁。范围适配器(如views::drop)返回的视图不会复制或持有底层数据,只会保存对数据源的引用。如果直接用prvalue的initializer_list创建视图,视图初始化完成后,底层数组已经被释放,后续任何对视图的操作都会触发未定义行为。为了从根源避免这种风险,C++标准库的范围适配器设计上会拒绝绑定到prvalue的initializer_list,直接导致编译失败。
至于views::all(initializer_list<int>{1,2,4})的写法:views::all并没有延长prvalue initializer_list生存期的能力,它返回的视图依然是引用原数据源,同样会面临数组销毁后的悬空问题,因此编译器也会报错。
解决方案
要让临时列表配合范围适配器正常工作,推荐改用持有自身数据的容器类型,比如std::array:
auto v2 = std::array{1,2,4} | views::drop(1);
std::array是值语义的容器,prvalue的array会被视图安全处理——它的生存期会自动覆盖视图的使用周期,不会出现悬空问题。
如果必须使用initializer_list,就保持将其绑定到左值变量的写法,确保底层数组的生存期覆盖视图的整个使用周期。
内容的提问来源于stack exchange,提问作者danielNJ

