C++如何用单一变量透明替换range为步长视图?
问题背景
出于性能优化需求,仅当数据量达到阈值时,我需要把原range替换为带步长的视图,同时希望后续代码完全不用感知这次替换,也不需要新增变量。
尝试与问题
我可以创建strode_view来存储步长处理后的视图,但这得新增变量。尝试直接赋值:
range = range | std::ranges::views::stride(range.size() / in_range_i_need_only);
失败了,因为原range是std::ranges::subrange类型,和右侧的stride_view类型不兼容。改用std::ranges::subrange初始化变量后,赋值还是失败,因为每次视图管道操作都会生成全新的类型。
需求
有没有办法只用一个变量完成这种替换,让后续代码不用关心range/view的类型变化?如果做不到,麻烦说明原因。
原代码
#include <execution> #include <iostream> #include <ranges> #include <vector> int main() { std::vector<float> values(10); std::ranges::iota(values, 0); auto range = std::ranges::subrange(values.begin(),values.end()); std::ranges::for_each(range, [](auto& v) { std::cout << v << " ";}); std::cout << std::endl; const int in_range_i_need_only = 3; std::ranges::for_each(range | std::ranges::views::stride(range.size() / in_range_i_need_only), [](auto& v) { std::cout << v << " ";}); std::cout << std::endl; auto strode_view = range | std::ranges::views::stride(range.size() / in_range_i_need_only); std::ranges::for_each(strode_view, [](auto& v) { std::cout << v << " ";}); // No way to assign here; is there a workaround? range = range | std::ranges::views::stride(range.size() / in_range_i_need_only); }
修改后的尝试代码
auto view = std::ranges::subrange(range); std::ranges::for_each(view, [](auto& v) { std::cout << v << " "; }); // No way to assign here; is there a workaround? view = view | std::ranges::views::stride(range.size() / in_range_i_need_only);
解决方案说明
在C的范围视图体系中,无法直接用单一静态类型变量完成这种替换,核心原因是:
C是静态类型语言,变量类型在编译期就已确定。而视图(比如stride_view)都是模板生成的轻量化包装,每个视图组合都会生成独一无二的具体类型,和原始subrange类型完全不兼容,无法直接赋值给同一静态类型变量。
不过可以通过两种方式实现类似需求,满足“单变量+后续代码无感知”的要求:
1. 使用std::ranges::any_view做类型擦除(C++20+)
any_view会擦除底层视图的具体类型,统一为一个通用的视图类型,允许直接赋值不同的视图,后续代码无需关心类型变化:
#include <execution> #include <iostream> #include <ranges> #include <vector> int main() { std::vector<float> values(10); std::ranges::iota(values, 0); // 用any_view擦除类型,指定元素为可修改引用 std::ranges::any_view<float&> range = std::ranges::subrange(values.begin(), values.end()); std::ranges::for_each(range, [](auto& v) { std::cout << v << " "; }); std::cout << std::endl; const int in_range_i_need_only = 3; // 直接赋值替换为带步长的视图,类型自动擦除 range = range | std::ranges::views::stride( static_cast<std::ranges::range_difference_t<decltype(range)>>(range.size() / in_range_i_need_only) ); // 后续遍历完全无感知类型变化 std::ranges::for_each(range, [](auto& v) { std::cout << v << " "; }); std::cout << std::endl; }
这种方式会带来轻微的运行时开销(虚函数调用),但完全符合“单变量+无感知”的需求,是最直接的解决方案。
2. 使用std::variant存储有限类型的视图
如果能提前确定所有可能的视图类型,可以用variant在单变量中存储不同类型,通过std::visit统一处理:
#include <execution> #include <iostream> #include <ranges> #include <vector> #include <variant> int main() { std::vector<float> values(10); std::ranges::iota(values, 0); using RawRange = std::ranges::subrange<decltype(values.begin())>; using StrideView = decltype(std::declval<RawRange>() | std::ranges::views::stride(1)); // 用variant存储原始range和步长视图两种类型 std::variant<RawRange, StrideView> range_or_view = RawRange(values.begin(), values.end()); // 原始遍历 std::visit([](auto&& r) { std::ranges::for_each(r, [](auto& v) { std::cout << v << " "; }); std::cout << std::endl; }, range_or_view); const int in_range_i_need_only = 3; // 替换为步长视图 range_or_view = std::get<RawRange>(range_or_view) | std::ranges::views::stride( std::get<RawRange>(range_or_view).size() / in_range_i_need_only ); // 后续遍历无需感知类型变化 std::visit([](auto&& r) { std::ranges::for_each(r, [](auto& v) { std::cout << v << " "; }); std::cout << std::endl; }, range_or_view); }
这种方式没有运行时开销,但需要提前枚举所有可能的视图类型,灵活性不如any_view。
总结
- 静态类型变量无法直接切换为不同的视图类型,因为C++编译期确定变量类型,而不同视图是完全独立的类型。
- 优先选择
std::ranges::any_view实现“单变量+无感知”需求,若在意运行时开销,可考虑std::variant方案。
内容的提问来源于stack exchange,提问作者Damir Tenishev

