1.核心基础概念
C++20 正式引入范围库 (std::ranges),是对传统 STL 迭代器 + 算法体系的重大升级。它基于概念 (Concept)、范围 (Range)、视图 (View)、管道语法、投影 (Projection) 五大核心能力,解决了传统 STL 写法繁琐、组合困难、易产生临时对象等问题,实现线性链式调用、惰性求值、零开销视图。
1.1.范围 (std::ranges::range)
范围是满足 range 概念的类型:只要类型拥有合法的 begin() 和 end(),返回迭代器 / 哨兵,就是一个范围。
- 所有标准容器(std::vector/std::string/std::map/ 数组)天然是范围;
- 范围描述一个可遍历的元素序列,不一定拥有实际数据。
传统 STL 必须传 begin()/end(),Ranges 算法直接接收整个范围,写法大幅简化。
C++标准库之std::begin、std::end、std::pre和std::next
1.2.哨兵 (Sentinel)
C++20 哨兵 (Sentinel) 是范围库 (Ranges) 引入的核心概念之一,是对传统迭代器模型的重大泛化。它彻底改变了序列结束位置的表示方式,允许迭代器与结束标记类型不同,从而支持无界范围、自定义终止条件、优化边界判断等场景,为现代 C++ 序列处理提供了极大灵活性。
哨兵是标记范围 (Range) 结束位置的对象,满足 std::sentinel_for<I> 概念,其中 I 是对应的起始迭代器类型。它的核心作用是与迭代器进行相等性比较,判断遍历是否结束,而不必与迭代器是同一类型。
C++20 范围的定义:一个对象只要提供 begin()(返回迭代器)和 end()(返回哨兵),且满足 sentinel_for<decltype(end()), decltype(begin())>,就是合法的范围。
与传统迭代器的根本区别:
| 特性 | 传统 STL 迭代器对 | C++20 迭代器 + 哨兵 |
|---|---|---|
| 类型要求 | begin() 和 end() 必须返回同类型迭代器 | end() 可返回任意类型哨兵,只要满足 sentinel_for 概念 |
| 结束判断 | 迭代器相等比较 | 迭代器与哨兵的自定义比较逻辑 |
| 适用场景 | 有限、已知边界的序列 | 有限 / 无限序列、自定义终止条件、输入流等 |
| 性能 | 边界判断固定为指针比较 | 可优化为更高效的判断逻辑(如直接检查值) |
核心概念:sentinel_for 与 sized_sentinel_for:
- std::sentinel_for<S, I>:核心约束,要求 S 类型对象可与 I 类型迭代器比较(operator==),判断是否到达序列末尾
- std::sized_sentinel_for<S, I>:增强约束,要求哨兵与迭代器之间可计算距离(operator-),支持 std::ranges::distance 等算法
简化示例:值边界哨兵
更简洁的哨兵实现,用于固定值边界判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
1.3.视图 (std::ranges::view)
视图是 Ranges 最核心的组件,也是和普通范围最大的区别:
- 轻量级、非拥有:视图不拷贝、不持有原始数据,仅对原有范围做 “遍历规则包装”;
- 廉价拷贝 / 移动:拷贝视图几乎无开销;
- 惰性求值 (Lazy):仅在实际遍历时才计算元素,链式组合不会产生中间临时容器;
- 不修改原数据:所有视图都是只读变换。
区分:范围 = 序列本身,视图 = 序列的 “透 视滤镜”。
1.4.借用范围 (borrowed_range)
用于保证临时范围可以被视图安全引用,避免悬垂引用。
例如 std::vector{1,2,3} | filter(...),临时容器生命周期会被视图合理托管。
1.5.管道语法|
Ranges 专属链式语法,格式:
原始范围 | 适配器1 | 适配器2 | 适配器3
等价于函数嵌套:适配器3(适配器2(适配器1(原始范围))),从左到右依次执行,代码线性可读。
2.std::ranges 整体分类
范围库全部位于 std::ranges 命名空间下,分为四大模块:
- 范围算法:对标传统 STL 算法,增强参数、支持范围 / 投影;
- 视图适配器:基于已有范围生成新视图(过滤、变换、截取等);
- 视图工厂:不依赖原有范围,直接生成全新范围(如连续数列);
- 范围概念:模板约束(range/view/input_range 等,用于模板编程)。
3.范围算法(std::ranges 算法)
3.1.语法差异
形式1:范围重载(推荐,日常首选)
传统 STL 算法:必须传递首尾迭代器
1 |
|
C++20 Ranges 算法:直接传入整个范围
1 2 3 4 5 |
|
形式2:迭代器 + 哨兵重载(兼容传统 / 自定义哨兵)
兼容旧式迭代器写法,同时原生支持迭代器与哨兵类型不同(C++20 哨兵特性),传统 STL 算法强制首尾迭代器同类型。
1 2 3 4 5 6 7 8 9 |
|
3.2.核心增强:投影 (Projection)
投影是一个可调用对象,接收单个元素,返回参与算法逻辑的值,全程只读、不修改原数据。 专门用于提取结构体成员、计算衍生值,大幅简化结构体 /std::pair 的排序、查找、统计逻辑。
绝大多数 Ranges 算法新增投影参数,用于提取元素的成员 / 属性,替代繁琐的 Lambda,是高频用法。
语法规则(通用):
1 |
|
- 投影函数:接收单个元素,返回用于比较 / 判断的值;
- 投影不会修改原元素,仅做 “取值映射”。
三种主流写法:
1.成员函数指针(最常用,结构体优先)
1 2 3 |
|
2.Lambda 表达式(自定义计算 / 复杂取值)
1 2 |
|
3.普通函数 / 函数对象(逻辑复用)
核心特点:
- 投影不修改原元素,仅做「值映射」;
- 算法先执行投影,再用投影结果做比较 / 判断;
- 所有谓词、比较器接收的都是投影后的值。