C++20 范围库详解
C++20 引入的范围库(Ranges Library) 是对 STL(标准模板库)的重大扩展和改进。它将迭代器和算法的概念提升到一个更高的抽象水平,使代码更简洁、更安全、更具可组合性。范围库的核心是范围(Range),它代表一个可迭代的元素序列,而不需要显式使用迭代器对(如 begin() 和 end())。范围库强调**视图(Views)**的懒惰计算(Lazy Evaluation),允许通过管道式组合适配器来处理数据,而不立即复制或修改底层容器。
范围库定义在头文件 <ranges> 中,并提供命名空间别名 std::views(等价于 std::ranges::views),用于简化视图适配器的使用。以下是范围库的详细讲解,结构包括:介绍、核心概念、范围概念、视图和适配器、算法、示例、最佳实践以及注意事项。内容基于 C++20 标准,并提及 C++23 的相关扩展。
1. 介绍
范围库的目的是解决传统 STL 迭代器模型的痛点:
- 传统迭代器的缺点:代码冗长(如手动传递
begin()和end())、易出错(迭代器失效、类型不匹配)、不易组合(算法难以链式调用)。 - 范围库的优势:
- 可组合性:通过视图适配器(如
views::filter | views::transform)构建管道,懒惰执行,提高可读性和效率。 - 安全性:使用概念(Concepts)在编译时检查类型要求,减少运行时错误。
- 兼容性:与现有 STL 容器和算法兼容,提供
ranges::命名空间下的重载版本,支持直接传入范围而非迭代器。 - 抽象水平:范围可以是迭代器对、计数序列(如
views::counted)、条件终止序列(如views::take_while)或无限序列(如views::iota)。
- 可组合性:通过视图适配器(如
范围库的核心组件包括:
- 范围访问函数:如
ranges::begin、ranges::end等,用于获取范围的迭代器和哨兵(Sentinel)。 - 视图:轻量级对象,代表对底层数据的非拥有视图,支持懒惰操作。
- 适配器:函数对象,用于创建视图,如
views::filter。 - 算法:
ranges::版本的 STL 算法,支持范围输入。 - 概念:如
ranges::range、ranges::view等,用于模板约束。
C++23 进一步扩展了范围库,添加了更多视图(如 ranges::repeat_view、ranges::zip_view 等)和实用功能(如 ranges::to 用于范围转换)。
2. 核心概念
- 范围(Range):任何提供
begin()和end()的类型都可以隐式转换为范围。范围是半开区间[begin, end)。 - 视图(View):一种特殊的范围,满足
ranges::view概念:常量时间拷贝/移动/赋值,常用于懒惰计算。视图不拥有数据,仅提供访问接口。 - 哨兵(Sentinel):范围的结束标记,可能与迭代器类型不同(如
views::take_while的哨兵基于谓词)。 - 悬垂迭代器处理(Dangling Iterator):引入
ranges::dangling类型,避免返回悬垂迭代器(如临时范围的end())。 - 借用范围(Borrowed Range):满足
ranges::borrowed_range概念的范围,其迭代器可以安全返回而不悬垂。 - 大小估算:
ranges::sized_range表示范围大小可在常量时间获取;ranges::approximately_sized_range表示可估算大小。 - 自定义点对象(CPO):如
ranges::begin,允许用户自定义范围行为。 - 范围原始类型:如
ranges::iterator_t<R>获取范围的迭代器类型;ranges::range_value_t<R>获取元素值类型。 - 其他实用工具:
ranges::subrange:将迭代器-哨兵对转换为视图。ranges::elements_of:将范围标记为序列而非单个值。- C++23 的
ranges::to:从输入范围构造新非视图对象(如auto vec = ranges::to<std::vector>(some_view);)。
范围库还引入了辅助模板,如 non-propagating-cache(类似 std::optional 但不传播常量性)和整数类型辅助(如 make-signed-like-t)。
3. 范围概念(Range Concepts)
范围库使用 C++20 概念进行编译时约束,确保模板安全。关键概念包括:
ranges::range:基本范围,提供begin和end。ranges::view:视图范围,常量时间拷贝/赋值。ranges::input_range/output_range/forward_range/bidirectional_range/random_access_range/contiguous_range:对应迭代器类型(从输入到连续)。ranges::common_range:迭代器和哨兵类型相同。ranges::sized_range:大小常量时间可得。ranges::borrowed_range:迭代器可安全借用。ranges::constant_range:元素只读。ranges::viewable_range:可安全转换为视图。
这些概念替换了预 C++20 的 SFINAE,提高了错误诊断。
4. 视图和适配器(Views and Adaptors)
视图是范围库的核心,提供懒惰视图工厂和适配器。适配器是函数对象(Range Adaptor Objects),可通过管道 | 组合,形成链式表达式。
4.1 范围工厂(Range Factories)(创建独立视图)
ranges::empty_view<T>:空视图,无元素。ranges::single_view<T>:包含单个元素的视图。ranges::iota_view:无限序列,从初始值递增(如views::iota(1)生成 1,2,3,…)。- C++23 的
ranges::repeat_view:重复相同值的无限序列。 ranges::basic_istream_view:从输入流读取元素的视图。
4.2 范围适配器(Range Adaptors)(转换现有范围)
适配器在 std::views 中定义,可链式使用。常见包括:
views::all:将范围转换为视图(默认适配)。views::ref:引用视图,不拥有数据。- C++23 的
views::owning:拥有范围的视图。 views::filter:过滤满足谓词的元素(ranges::filter_view)。views::transform:对每个元素应用函数(ranges::transform_view)。views::take/views::take_while:取前 N 个或直到谓词为假。views::drop/views::drop_while:跳过前 N 个或直到谓词为假。views::join/ C++23 的views::join_with:扁平化范围的范围(加入分隔符)。views::split/views::lazy_split:按分隔符分割。views::reverse:反转视图。views::common:转换为公共范围(迭代器和哨兵类型相同)。views::elements<N>/views::keys/views::values:提取元组/对的元素(keys为第一元素,values为第二)。- C++23 的扩展:
views::enumerate:添加索引(如(index, value))。views::zip/views::zip_transform:并行多个范围,应用变换。views::adjacent/views::adjacent_transform:相邻元素元组。views::chunk/views::chunk_by:分块视图。views::slide:滑动窗口。views::stride:步长跳跃。views::cartesian_product:笛卡尔积。views::as_const/views::as_rvalue:转换为常量或右值视图。
适配器是闭包对象(Range Adaptor Closure Objects),支持柯里化(如 auto f = views::filter(pred);)。
视图基类 ranges::view_interface 使用 CRTP(Curiously Recurring Template Pattern)简化自定义视图。
5. 算法(Algorithms)
范围库提供 std::ranges:: 命名空间下的算法重载,支持直接传入范围,而非迭代器对。这些算法是急切的(Eager),立即执行操作。它们与传统 <algorithm> 中的算法类似,但更安全和通用。
5.1 算法分类
- 非修改序列操作:
ranges::find、ranges::find_if、ranges::count、ranges::mismatch、ranges::equal、ranges::for_each等。 - 修改序列操作:
ranges::copy、ranges::move、ranges::transform、ranges::replace、ranges::fill、ranges::generate、ranges::remove、ranges::unique、ranges::reverse、ranges::rotate、ranges::shuffle、ranges::sample等。 - 分区和排序:
ranges::is_partitioned、ranges::partition、ranges::stable_partition、ranges::sort、ranges::stable_sort、ranges::partial_sort、ranges::nth_element、ranges::is_sorted等。 - 二分搜索:
ranges::lower_bound、ranges::upper_bound、ranges::equal_range、ranges::binary_search、ranges::is_heap等。 - 集合操作:
ranges::merge、ranges::inplace_merge、ranges::includes、ranges::set_union、ranges::set_intersection、ranges::set_difference、ranges::set_symmetric_difference等。 - 堆操作:
ranges::make_heap、ranges::push_heap、ranges::pop_heap、ranges::sort_heap等。 - 最小/最大:
ranges::min、ranges::max、ranges::minmax、ranges::clamp、ranges::lexicographical_compare等。 - 排列:
ranges::is_permutation、ranges::next_permutation、ranges::prev_permutation等。 - 数值算法(
<numeric>):ranges::iota、ranges::accumulate、ranges::reduce、ranges::inner_product、ranges::partial_sum、ranges::exclusive_scan、ranges::inclusive_scan、ranges::transform_reduce、ranges::transform_exclusive_scan、ranges::transform_inclusive_scan、ranges::adjacent_difference等。 - 未初始化内存(
<memory>):ranges::uninitialized_copy、ranges::uninitialized_fill、ranges::uninitialized_move、ranges::destroy、ranges::construct_at等。
5.2 与传统算法的区别
- 支持范围输入:如
ranges::sort(vec);而非std::sort(vec.begin(), vec.end());。 - 投影(Projection):许多算法支持投影函数,如
ranges::sort(vec, {}, &Person::age);(按年龄排序)。 - 返回值:许多算法返回结构化结果,如
ranges::find返回{it, end}的子范围。 - 并行执行:支持
std::execution::par等策略(C++17 扩展)。
C++23 未直接添加新算法,但增强了范围转换(如 ranges::to)以更好地与算法集成。
6. 示例
以下是完整、可编译的示例(需 C++20+,如 g++ -std=c++20)。示例展示视图组合、算法使用和益处。
示例 1: 基本视图管道(过滤和变换)
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6};
// 视图管道:过滤偶数,加倍,反转
auto view = vec | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * 2; })
| std::views::reverse;
// 懒惰计算:仅在迭代时执行
for (int x : view) {
std::cout << x << " "; // 输出: 12 8 4
}
std::cout << std::endl;
return 0;
}
益处:比传统迭代器代码更简洁,无需手动循环。
示例 2: 算法与范围
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm> // for ranges::sort 等
int main() {
std::vector<int> vec = {5, 3, 1, 4, 2};
// 范围排序
std::ranges::sort(vec);
// 输出: 1 2 3 4 5
for (int x : vec) {
std::cout << x << " ";
}
std::cout << std::endl;
// 查找子范围
auto result = std::ranges::find_if(vec, [](int x) { return x > 3; });
if (result != vec.end()) {
std::cout << "First >3: " << *result << std::endl; // 输出: 4
}
return 0;
}
益处:直接传入容器,自动处理范围。
示例 3: C++23 扩展(zip 和 enumerate)
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> a = {1, 2, 3};
std::vector<char> b = {'a', 'b', 'c'};
// zip 两个范围
auto zipped = std::views::zip(a, b);
for (auto [x, y] : zipped) {
std::cout << x << y << " "; // 输出: 1a 2b 3c
}
std::cout << std::endl;
// enumerate 添加索引
auto enumerated = std::views::enumerate(a);
for (auto [idx, val] : enumerated) {
std::cout << idx << ":" << val << " "; // 输出: 0:1 1:2 2:3
}
std::cout << std::endl;
// ranges::to 转换为容器
auto vec_from_view = std::ranges::to<std::vector>(zipped | std::views::transform([](auto t) { return std::get<0>(t) + 10; }));
for (int x : vec_from_view) {
std::cout << x << " "; // 输出: 11 12 13
}
std::cout << std::endl;
return 0;
}
注意:需 C++23 支持。
示例 4: 无限范围与 take
#include <iostream>
#include <ranges>
int main() {
// 无限序列,取前 10 个
auto infinite = std::views::iota(1) | std::views::take(10);
for (int x : infinite) {
std::cout << x << " "; // 输出: 1 2 3 4 5 6 7 8 9 10
}
std::cout << std::endl;
return 0;
}
7. 最佳实践
- 优先使用视图管道:链式组合适配器,提高代码可读性(如
vec | views::filter | views::transform)。 - 懒惰 vs 急切:视图懒惰计算,适合大数据集;算法急切执行,适合小规模修改。
- 概念约束:在自定义模板中使用范围概念,如
requires ranges::random_access_range<R>。 - 避免悬垂:使用
borrowed_range或dangling处理临时范围。 - 性能优化:视图零开销抽象,但链太长可能影响编译时间;使用
views::common确保兼容传统算法。 - 与 STL 集成:范围算法可与传统迭代器混合,但优先范围版本。
- C++23 迁移:利用
ranges::to简化视图到容器的转换;探索新视图如zip用于并行处理。 - 调试:使用
static_assert检查概念;避免在视图中修改底层数据(视图常为只读)。
8. 注意事项
- 兼容性:需 C++20+ 编译器(如 GCC 10+、Clang 10+、MSVC 19.28+)。C++23 功能需更新版本。
- 性能开销:视图懒惰,但复杂链可能增加运行时开销;测试大数据集。
- 限制:并非所有 STL 算法都有范围版本;无限范围需小心,避免无限循环。
- 自定义范围:继承
view_interface实现自定义视图,确保满足概念。 - 更新:到 2025 年,C++23 已标准化,C++26 可能添加
views::concat等,但范围库核心稳定。
更多推荐


所有评论(0)