C++/ranges
<ranges>是C++20引入的标准库。<ranges>中定义了std::ranges和std::views命名空间。
“范围”(range)是一个可以迭代的对象的集合,支持begin()和end()迭代器的结构都是范围。这包括大多数STL容器。
“视图”(view)从底层的范围返回数据,但不拥有任何数据,并通过某种算法或操作转换底层的范围。视图是惰性的,只在范围迭代(即请求元素)时才会惰性求值,而不是在创建视图时执行。view是容器的视图,具有一定的数据操作能力,而且还重载了"|",能够实现多个view的串联操作。
视图适配器是一个对象,可接受一个范围,并返回一个视图对象。视图适配器可以使用|操作符连接到其他视图适配器。视图适配器从|操作符的左侧获得范围操作数。|操作符会从左到右求值。视图适配器位于std::views命名空间中。
一个简单例子:
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int>vi{0,1,2,3,4,5,6,7,8,9};
auto tview =vi|std::views::reverse|std::views::take(5); //视图适配器
for(int i:tview)
std::cout<<i<<" ";
//输出为:9 8 7 6 5
// 不会产生数据
auto view = std::views::iota(0, 10);
// 开始产生数据
for (auto i : view)
{
std::cout << i << std::endl;
}
return 0;
}
ranges库在std::views命名空间中包括一些变换函数:
- take(int)选出view里的前N个元素。如果改编后的视图包含的元素少于N,则返回所有元素。
- take_while()给定一个一元谓词 pred 和一个视图 r,它生成一个范围 [begin(r), ranges::find_if_not(r, pred)) 的视图。
- reverse()逆序双向视图里的所有元素
- filter(invokable)视图使用一个谓词函数筛选出view里的特定元素,例如
auto result = nums|std::views::filter([](int i){return 0==i%2;});
- transform(invokable)视图使用了一个转换函数,对每个元素应用转换函数后,返回底层序列的视图。例如
auto result =nums|std::views::transform([](int i){return i*i;});
- drop()排除view里的前N个元素,如果改编后的视图包含少于 N 个元素,则返回一个空范围。例如:
rnums = std::views::drop(rnums,5);
- drop_while()给定一个一元谓词 pred 和一个视图 r,它生成一个范围 [ranges::find_if_not(r, pred), ranges::end(r)) 的视图。
- keys()选出pair里的first成员。是elements_view<views::all_t<R>, 0> 的别名。
- values()选出pair里的second成员。是elements_view<views::all_t<R>, 1>的别名。
- all() 返回一个包含传入的 range 参数的所有元素的视图
- join()将一个范围视图平展为一个视图
- split(forward_range & view)接受一个视图和一个分隔符,并将视图划分为分隔符上的子范围。分隔符可以是单个元素,也可以是元素的视图。
- common()或common_range()获取一个迭代器和哨兵具有不同类型的视图,并将其转换为具有相同类型迭代器和哨兵的相同元素的视图。对于调用期望范围的迭代器和哨点类型相同的遗留算法很有用。
- counted()计数视图显示了迭代器i和非负整数 n 的计数范围([iterator.requirements.general]) i+[0, n) 的元素的视图。
- elements()接受一个类元组值和 size_t 的视图,并生成一个值类型为已改编视图值类型的第 n 个元素的视图。
- zip(C++23) 由对已改编视图的相应元素的引用的元组组成的视图
- zip_transform(C++23) 由转换函数应用到所适应视图的相应元素的结果元组组成的视图
- adjacent(C++23) 由对已改编视图的相邻元素的引用元组组成的视图
- adjacent_transform(C++23) 由转换函数应用于所适应视图的相邻元素的结果元组组成
- join_with(C++23) 一种视图,由将范围视图平展得到的序列组成,元素之间有分隔符
- slide(C++23) 第 M 个元素是另一个视图的第 M 个到 (M + N - 1) 个元素的视图
- chunk(C++23) 一个由另一个视图的元素组成的n个大小的不重叠连续块的视图范围
- chunk_by(C++23) 将视图拆分为给定谓词返回false的每对相邻元素之间的子范围
- as_const(C++23) 将视图转换为常量范围
- as_rvalue(C++23) 将每个元素强制转换为右值的序列视图
- stride(C++23) 由另一个视图的元素组成的视图,一次向前移动N个元素
ranges库还包括一些factory函数:
- 对象std::views::empty, void -> view, 使用std::views::empty<T>即可直接获得对象
- 对象std::views::single, any -> view, 单对象的std::ranges::view
- 对象std::views::iota, iterator | (iterator, sentinel) -> view, 一般哨位边界的有限或无限递增序列 例如,itoa将生成一系列递增的值,如
auto rnums =std::views::iota(1,10);
- 对象std::views::counted, (iterator, count) -> view, 计数哨位边界的有限递增序列
- 对象std::views::istream<T>, istream<T> -> view, 输入流转std::ranges::view
- 类型std::ranges::subrange, (iterator, sentinel, [size]) | (borrowed_range, [size]) -> subvrange, 迭代器-哨位对
- 类型std::ranges::ref_view, range -> viewable_range 借用
- 类型std::ranges::owning_view, range -> viewable_range 占用
- 对象std::views::repeat(C++23), 由重复产生相同值的生成序列组成的视图
- 对象std::views::cartesian_product(C++23), 由n元笛卡尔积计算出的结果元组组成的视图
range是容器上的一个抽象概念,可以理解成指明首末位置的迭代器,即pair<begin,end>,这样range自身就包含能用于算法的足够信息,大多数算法只要用一个range参数就可以工作。基于range的概念,C++在名字空间std::ranges提供了与标准算法同名、但却使用range参数的算法,写法很简洁。从C++20开始,<algorithm>头文件中的大多数算法都会基于“范围”。这些版本在<algorithm>头文件中,但是在std::ranges命名空间中,这使它们与传统算法分离开。所以无需再调用两个迭代器的算法。例如std::sort(v.begin(),v.end());
可以用范围来调用std::ranges::sort(v);
使用试图适配器更为直观、易读:std::ranges::sort(v|std::ranges::view::reverse|std::ranges::views::drop(5));
range的分类
[编辑]Concept | Description |
---|---|
std::ranges::input_range | can be iterated from beginning to end at least once |
std::ranges::forward_range | can be iterated from beginning to end multiple times |
std::ranges::bidirectional_range | iterator can also move backwards with --
|
std::ranges::random_access_range | can jump to elements in constant-time []
|
std::ranges::contiguous_range | elements are always stored consecutively in memory |
std::ranges::sized_range | 范围的大小可由std::ranges::size()获得,提供size函数,能常数时间获取范围的长度;如果未提供size函数, 那么还要求
std::forward_iterator且iterator与sentinel(及iterator)可作差;如果size或者iterator的作差不能用常数时间实现, 那么可以用特化:std::ranges::disable_sized_range<T> = true且std::disable_sized_sentinel_for<iterator, iterator> = true, 强制关闭std::ranges::sized_range和std::ranges::sized_sentinel_for的特征 |
std::ranges::common_range | 如果iterator == sentinel |
std::ranges::viewable_range | 如果std::ranges::view 或std::ranges::borrowed_range |
std::ranges::borrowed_range | 如果类型std::ranges::range T的值T t的t.begin()和t.end()获得的迭代器的生命周期与t无关, 那么可以认为是std::ranges::borrowed_range。由于语言层面无法自动识别生命周期的关系, 因此要特征能被识别, 还要手动特化std::ranges::enable_borrowed_range<T>为true |