std::views::filter 和 std::views::transform 是惰性、零拷贝的视图适配器,需链式调用、避免误当容器使用,注意生命周期、顺序组合与底层数据有效性。

用 std::views::filter 做条件筛选,别写 for 循环
直接在 range 上链式调用 std::views::filter,它返回一个惰性视图,不拷贝原始数据,也不立即执行——只有你遍历时才逐个判断。常见错误是误以为它会生成新容器,结果后续想取 .size() 或随机访问就失败。
比如过滤偶数:
auto even_view = std::views::iota(1, 10) | std::views::filter([](int x) { return x % 2 == 0; });注意:even_view 是 std::ranges::filter_view 类型,不是 std::vector;不能用 even_view[3],但可以用 for (int x : even_view) 安全遍历。
- 如果需要转成容器(如
std::vector),得显式构造:std::vector<int>(even_view.begin(), even_view.end())</int> - lambda 捕获变量要小心:若捕获局部引用或临时对象,视图生命周期短于其使用,运行时可能崩溃
-
filter不保证稳定性(虽然通常保持顺序),但对数组/向量这类 random access range,顺序一定不变
用 std::views::transform 替代手写映射逻辑
std::views::transform 同样惰性、零拷贝,把每个元素喂给函数,产出新视图。和 filter 组合时,顺序很重要:先 filter 再 transform 更高效,避免对被过滤掉的元素做无谓计算。
立即学习“C++免费学习笔记(深入)”;
例如把正偶数平方:
auto result = std::views::iota(-5, 10)
| std::views::filter([](int x) { return x > 0 && x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });关键点:
- 传入的 lambda 必须是可调用对象,且返回类型能被推导(不要用
auto返回类型除非用 C++23 deduction guides) - 若转换函数抛异常,遍历时才会触发——调试时容易漏掉,建议在 transform 内部加简单断言或日志
- 对
std::array或原生数组使用时,需先用std::views::all包一层:std::views::all(my_array) | ...
组合多个操作时,优先用管道符 | 而非嵌套函数调用
管道符不是语法糖,它让类型推导更自然、编译错误更易读。嵌套写法如 transform(filter(...), ...) 容易导致模板参数爆炸,报错信息动辄几百行。
典型场景:从字符串数组中挑出长度 > 3 的,转大写,再取前 5 个:
std::vector<std::string> words = {"a", "hello", "hi", "world", "cpp"};
auto processed = std::views::all(words)
| std::views::filter([](const auto& s) { return s.size() > 3; })
| std::views::transform([](const auto& s) {
std::string upper = s;
std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
return upper;
})
| std::views::take(5);注意:
-
std::views::take(5)是有界视图,支持size()和随机访问(如果上游支持) - 上面
transform中返回的是临时std::string,视图里存的是值,安全;但如果返回局部char*或引用,就悬空 - 所有 views 默认不可修改底层数据,若需原地修改,得用
std::ranges::for_each或传统循环
真实数组(int arr[N])怎么接入 ranges?
原生数组不是 range,必须显式转。最简方式是用 std::views::all(arr),它返回一个适配器,支持 begin/end 和 size。
例如处理 C 风格数组:
int data[] = {1, 2, 3, 4, 5, 6};
auto evens_squared = std::views::all(data)
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });坑点很实在:
- 别写
std::views::all(data + 0)或std::span(data)——前者退化为指针,丢失长度;后者虽可用,但不如all直接 - 数组大小必须在编译期可知(即不能是动态分配的
new int[n]),否则all推不出 end 迭代器 - 若数组生命周期短于视图使用周期(比如函数内定义数组,返回视图),视图会悬空——这是最隐蔽的崩溃源
C++20 ranges 的“优雅”本质是表达力与惰性的平衡,但所有视图都依赖底层数据的生存期;一旦忘了这点,再漂亮的链式调用也会在运行时静默失效。


















