Range-v3场景下为何此处必须使用ranges::to_vector?
问题原因分析
1. 未加第一个to_vector时intermediate2转vector后values全为0的原因
- 未加第一个
to_vector时,intermediate1是zip_view,它的元素是prvalue临时pair:第一个成员是iota生成的无符号值临时对象,第二个成员是指向input容器元素的float&引用。 views::partial_sum是惰性求值视图,仅在真正遍历元素时才计算结果。部分range库版本(尤其是range-v3)处理临时prvalue元素作为输入时,累加过程中容易出现临时对象生命周期提前结束、值拷贝异常的问题,最终导致float字段被错误赋值为0。- 加了第一个
to_vector后,intermediate1变成存储pair<unsigned, float>值类型的vector,所有元素都是持久化的左值,不存在引用临时对象的问题,计算自然恢复正常。
2. 末尾取消views::reverse时报错的原因
views::reverse要求输入范围必须满足双向范围(bidirectional range)概念,也就是迭代器支持--操作。但views::partial_sum的实现原理是从范围头部开始累加计算每个位置的结果,无法直接倒序遍历,因此它返回的范围最多是前向范围(forward range),不满足reverse的要求,直接调用会编译报错。
解决方案
可以避免使用第一个to_vector
不需要额外分配vector存储中间zip结果,你可以在zip之后加一个转换视图,把临时prvalue pair转成值类型,消除引用临时对象的问题,全程无额外内存分配,性能远高于to_vector:
auto intermediate1 = views::zip(views::iota(0u, COUNT), input) | views::transform([](auto&& p) { return std::pair<unsigned, float>(p.first, p.second); });
正确的完整实现流程
因为无法直接对partial_sum的结果执行reverse,你必须先把partial_sum的结果转成vector再reverse,参考代码如下:
auto input = std::vector<float>{} | actions::push_back(views::iota(0u, COUNT)) | actions::shuffle(std::default_random_engine{}); auto intermediate1 = views::zip(views::iota(0u, COUNT), input) | views::transform([](auto&& p) { return std::pair<unsigned, float>(p.first, p.second); }); auto intermediate2 = intermediate1 | views::reverse | views::partial_sum( [](std::pair<unsigned, float> a, std::pair<unsigned, float> b) { return a.second > b.second ? b : a; }) | to_vector; // 这里必须转vector,因为后续要执行reverse auto ans = intermediate2 | views::reverse;
注意事项
绝对不建议盲目加to_vector直到代码跑通:
- 不必要的
to_vector会带来额外的内存分配和全量元素拷贝,大幅抵消range视图的性能优势。 - 盲目加
to_vector只是掩盖了生命周期、range概念不匹配的底层问题,后续修改代码时很容易再次出现诡异bug。
你只需要记住两个必须加to_vector的典型场景即可:
- 视图的计算结果需要持久化保存,或者后续需要多次遍历避免重复计算。
- 后续操作要求更高的range概念(比如需要双向/随机访问范围,而当前视图只支持前向/输入范围)。
内容的提问来源于stack exchange,提问作者Tom Huntington
相关产品推荐
相关产品推荐

