Appearance
C++20 ranges(范围库):管道视图、算法、view
C++20 让算法“声明式地链式组合”,核心是视图(view,惰性求值、不拷贝) 与 受约束算法。Infra 场景处理数组重/数据变换密集,ranges 的掌控是加分项。
一、核心概念
- Range:可 begin/end 的取值范围(数组/容器/任何给 begin/end 的东西)。
- View:轻量、可拷贝、惰性(lazy)的 range,组合不拷贝底层数据,遍历时才计算转换(类似 lazy 管道)。
- 受约束算法:如
ranges::sort、ranges::transform——接收 range 直接且约束更好地错误诊断,也接受投影。
二、管道(|)链式表达(签名)
cpp
#include <ranges>
#include <vector>
std::vector<int> v{5,1,4,2,3,1};
auto r = v
| std::views::filter([](int x){ return x % 2 == 0; }) // 只留偶数
| std::views::transform([](int x){ return x * x; }); // 平方
for (int e : r) /* 惰性:边遍历边计算,不建临时数组 */;说明:全程不构造中间 vector;每次求值一个元素,缓存友好且不重复扫内存(如 filter 内部逐元素暂停推进)。
三、常用 views速查
| View | 作用 |
|---|---|
filter | 按谓词保留 |
transform | 元素映射(可能改变长度语义为 1:1) |
take(n) / drop(n) | 取前/丢前 n |
iota(start, stop) | 单调整型生成器(可当索引循环,编译期 static_assert 也能用) |
reverse / take_while / drop_while | 方向/条件截断 |
join / split/common | 展平嵌套 / 切分 |
views::all | 显式作为 view |
stride (P1899 C++23 方向) | 取步长元素(类似 SIMD 中 stride access) |
四、受约束算法 + 投影(projection)(高阶加分)
cpp
struct Person { std::string name; int age; };
std::vector<Person> ps;
std::ranges::sort(ps, {}, &Person::age); // 第三个参是投影:按 age 排
// 等价 sort(ps, less, [](auto&p){return p.age;})- 排序仍调用底层比较,但投影避免写 lambda 取字段。
- ranges 算法要求按 concept 精确(如 random_access_range),参数表达简化。
五、为什么对 HPC/性能题有吸引力(讲清取舍)
- 惰性避免一遍遍构建中间容器 → 少内存分配与拷贝 → 数据量大时很契合。
- 隐患:
- 不总是比手写 for 快——chain 会产生(可能被内联掉的)虚/回调 + 边界判断;数据量小时不如简单循环。务必 profile。
- view 不要存储/越界返回:它们是 borrowed_range,引用的容器生命期必须大于 view。
- 某些链式 view(如 drop/filter)迭代器不是 random access,退化的索引查找/反向不可行。
- 编译器对 view 短链通常能优化成接近手写;过度链反而阻优化。
- 因此给“什么时候适合用”:表达管道、明确不建临时、遍历一次即可 → ranges;对最内层热内核追求极致 → 常手写 explicit 循环或用指针内核。
六、C++23 提示
std::views::zip(元组/并行迭代)在 C++23,之前常用 index zip 推断。答“知道 zip/enumerate 方向在 C++23”加分。
七、自测
take与filter先谁后?filter 之后 take 语义(取前几个 满足 filter的)——链顺序决定语义。- view 产生的 range 能否传给会改底层容器的算法?—— borrow 语义,注意别让 view 越过容器生命。
- 如何序列化“对偶数取平方”且不分配中间 vector?—— view 链惰性即可。