前言
C++14/17/20 的范围库。此代码是 C++20 中范围支持的基础。
开发状态
这些代码相当稳定,经过充分测试,适合随意使用,尽管目前缺乏文档。不做出任何有关支持或长期稳定性的承诺。该代码将不断发展,而不考虑向后兼容性。
一个需要注意的例外是在 range::cpp20 命名空间中找到的任何内容。这些组件很少改变或者(最好)永远不会改变。
安装
该库仅包含头文件。您可以从 github 上的 range-v3 存储库获取源代码。要使用 Range-v3 进行编译,只需 #include 您想要的各个标头。
该发行版实际上包含三个独立的仅头文件库:
- include/concepts/... 包含概念可移植性预处理器 (CPP),它是一组用于定义和使用概念检查的宏,无论您的编译器是否恰好支持 C++20 概念语言功能。
- include/meta/... 包含元库,它是一组元编程实用程序,用于在编译时处理类型和类型列表。
- include/range/... 包含 Range-v3 库,如下所述。
Range-v3 库的物理结构按功能组划分在目录中:
- include/range/v3/actions/... 包含操作或可组合组件,它们在容器上急切地操作并返回变异容器以进行进一步操作。
- include/range/v3/algorithms/... 除了熟悉的采用迭代器的重载之外,还包含所有具有接受范围重载的 STL 算法。
- include/range/v3/functional/... 包含许多函数式程序员熟悉的常用组件。
- include/range/v3/iterator/... 包含许多有用的迭代器以及与迭代器相关的概念和实用程序的定义。
- include/range/v3/numeric/... 包含与标准 <numeric> 标头中的算法相对应的数值算法。
- include/range/v3/range/... 包含与范围相关的实用程序,例如开始、结束和大小、范围特征和概念以及容器的转换。
- include/range/v3/utility/... 包含各种可重用代码。
- include/range/v3/view/... 包含视图或可组合组件,它们在范围上延迟操作,并且本身返回可以使用附加视图适配器进行操作的范围。
许可
这个项目中的大部分源代码都是我的,并且都在 Boost Software License 下。 部分内容取自 Alex Stepanov 的 Elements of Programming、Howard Hinnant 的 libc 以及 SGI STL。 请参阅随附的许可证文件和 CREDITS 文件以获取许可和确认。
支持的编译器
已知该代码可在以下编译器上运行:
- clang 5.0
- GCC 6.5
- Clang/LLVM 6 (or later) on Windows
- MSVC VS2019, with /permissive- and either /std:c++latest, /std:c++20, or /std:c++17
快速开始
Range-v3 是一个通用库,它通过使用范围的工具来增强现有的标准库。 范围可以粗略地认为是一对迭代器,尽管它们不需要以这种方式实现。 将开始/结束迭代器捆绑到单个对象中会带来几个好处:方便、可组合性和正确性。
方便
将单个范围对象传递给算法比单独的开始/结束迭代器更方便。比较:
std::vector<int> v{/*...*/};
std::sort( v.begin(), v.end() );
与
std::vector<int> v{/*...*/};
ranges::sor( v );
Range-v3 包含所有标准算法的完整实现,并具有基于范围的重载,以方便使用。
可组合
拥有单个范围对象允许操作的管道。 在管道中,范围以某种方式进行延迟适应或急切突变,结果立即可用于进一步适应或突变。 延迟适应由视图处理,急切突变由操作处理。
例如,下面使用视图通过谓词过滤容器,并使用函数转换结果范围。请注意,底层数据是常量,不会因视图而改变。
std::vector<int> const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
using namespace ranges;
auto rng = vi | views::remove_if([](int i) { return i % 2 == 1; })
| views::transform([](int i) { return std::to_string(i); });
// rng == {"2","4","6","8","10"};
在上面的代码中,rng 只是存储对基础数据以及过滤器和转换函数的引用。在迭代 rng 之前不会完成任何工作。
相比之下,动作热切地完成它们的工作,但它们也构成了。考虑下面的代码,它将一些数据读入向量,对其进行排序,并使其唯一。
extern std::vector<int> read_data();
using namespace ranges;
std::vector<int> vi = read_data() | actions::sort | actions::unique;
与视图不同,管道中的每个步骤(actions::sort 和 actions::unique)都通过操作按值接受容器,就地改变它,然后返回它。
正确性
无论您使用视图还是操作,您都是以纯函数式、声明式的方式操作数据。您很少需要为迭代器烦恼,尽管如果您需要它们,它们就在幕后。
通过以声明性和功能性方式而不是命令性地操作,我们减少了对公开状态操作以及分支和循环的需求。这会减少程序可以处于的状态数量,从而减少错误数量。
简而言之,如果您能找到一种方法将您的解决方案表达为数据上的函数转换的组合,则可以通过构造使您的代码正确。
视图(Views)
如上所述,范围相对于迭代器的一大优点是它们的可组合性。它们允许一种函数式编程,其中通过一系列组合器传递数据来操纵数据。此外,组合器可以是惰性的,仅在请求答案时才工作,并且是纯函数式的,不会改变原始数据。这使得你更容易推理你的代码。
视图是一种轻量级包装器,它以某种自定义方式呈现底层元素序列的视图,而无需对其进行更改或复制。视图的创建和复制成本低廉,并且具有非拥有引用语义。以下是一些使用视图的示例:
使用谓词过滤容器并对其进行转换。
std::vector<int> const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
using namespace ranges;
auto rng = vi | views::remove_if([](int i){return i % 2 == 1;})
| views::transform([](int i){return std::to_string(i);});
// rng == {"2","4","6","8","10"};
生成从 1 开始的无限整数列表,对它们求平方,取前 10 个,并对它们求和:
using namespace ranges;
int sum = accumulate(views::ints(1)
| views::transform([](int i){return i*i;})
| views::take(10), 0);
使用范围推导式动态生成一个序列,并用它初始化一个向量:
using namespace ranges;
auto vi = views::for_each(views::ints(1, 10), [](int i) {
return yield_from(views::repeat_n(i, i));
})
| to<std::vector>();
// vi == {1,2,2,3,3,3,4,4,4,4,5,5,5,5,5,...}
视图的常量性
从逻辑上讲,视图是迭代器的工厂,但实际上视图通常被实现为状态机,状态存储在视图对象本身中(以保持迭代器较小),并在视图迭代时发生变化。因为视图包含可变状态,所以许多视图缺少 const 限定的begin()/end()。当提供 begin()/end() 的 const 版本时,它们是真正的 const;也就是说,线程安全。
由于视图呈现了与容器相同的接口,因此很容易认为它们在常量性方面的行为与容器相似。事实并非如此。它们在常量性方面的行为类似于迭代器和指针。
视图的常量性与基础数据的常量性无关。 非常量视图可以引用本身是常量的元素,反之亦然。 这类似于指针; int* const 是指向可变 int 的 const 指针,int const* 是指向 const int 的非常量指针。
尽可能使用非常量视图。如果您需要线程安全,请在线程中使用视图副本;不要共享。
视图的有效性
对基础范围进行的任何使其迭代器或标记无效的操作也将使引用该范围任何部分的任何视图无效。 此外,当范围的底层元素发生变化时,某些视图(例如,views::filter)会失效。 最好在任何可能改变底层范围的操作之后重新创建视图。
视图列表
下面是 Range-v3 提供的惰性范围组合器或视图的列表,以及关于如何使用每个组合器的简介。
-
views::addressof
给定左值引用的源范围,返回一个新视图,该视图是每个左值引用的std::addressof
的结果。 -
views::adjacent_filter
对于源范围中的每对相邻元素,计算指定的二元谓词。如果谓词计算结果为 false,则从结果范围中删除该对的第二个元素;否则,它被包括在内。 -
views::adjacent_remove_if
对于源范围中的每对相邻元素,计算指定的二元谓词。 如果谓词计算结果为 true,则从结果范围中删除该对的第一个元素; 否则,它被包括在内。 始终包含源范围中的最后一个元素。 -
views::all
返回包含源中所有元素的范围。对于将容器转换为范围很有用。 -
any_view<T>(rng)
值类型为 T 的元素的类型擦除范围;可以使用此值类型存储任何范围。 -
views::c_str
将 \0 终止的 C 字符串(例如来自 const char*)视为范围。 -
views::cache1
缓存视图中的最新元素,以便多次取消引用视图的迭代器不会导致任何重新计算。 例如,这在包括 view::filter 和 view::transform 组合的适配器管道中非常有用。 views::cache1 始终是单通道。 -
views::cartesian_product
枚举 n 个范围的 n 元笛卡尔积,即生成所有 n 元组 (e1, e2, ... , en),其中 e1 是第一个范围的元素,e2 是第二个范围的元素,依此类推。 -
views::chunk
给定源范围和整数 N,生成一系列连续范围,其中每个内部范围都有 N 个连续元素。最终范围的元素可能少于 N 个。 -
views::common
将源范围转换为公共范围,其中结束的类型与开始的类型相同。对于调用 std:: 命名空间中的算法很有用。 -
views::concat
给定 N 个源范围,生成一个由所有源范围串联而成的结果范围。 -
views::const_
呈现源范围的常量视图。 -
views::counted
给定一个迭代器 it 和一个计数 n,创建一个从 it 开始并包含接下来的 n 个元素的范围。 -
views::cycle
返回无限重复源范围的范围。 -
views::delimit
给定源范围和值,返回一个新范围,该范围在源末尾或该值第一次出现时结束(以先到者为准)。 或者,可以使用迭代器和值调用 views::delimit,在这种情况下,它返回一个从指定位置开始到该值第一次出现为止的范围。 -
views::drop
给定源范围和整数计数,返回一个由源范围中除第一个 count 元素之外的所有元素组成的范围,如果元素较少,则返回空范围。 -
views::drop_last
给定一个源范围和一个整数计数,返回一个由源范围中除最后一个 count 元素之外的所有元素组成的范围,如果元素较少,则返回一个空范围。 -
views::drop_exactly
给定源范围和整数计数,返回一个由源范围中除第一个计数元素之外的所有元素组成的范围。源范围必须至少包含那么多元素。 -
views::drop_while
从范围前面删除满足一元谓词的元素。 -
views::empty
使用给定值类型创建一个空范围。 -
views::enumerate
将范围的每个元素与其索引配对。 -
views::filter
给定源范围和一元谓词,过滤满足谓词的元素。 (对于 Boost.Range 的用户来说,这就像过滤器适配器。) -
views::for_each
延迟地将一元函数应用于返回另一个范围(可能为空)的源范围中的每个元素,从而展平结果。 -
views::generate
给定一个零函数,返回一个无限范围,其元素是用该函数生成的。 -
views::generate_n
给定一个空函数和一个计数,返回一个范围,通过调用该函数生成所需数量的元素。 -
views::chunk_by
给定一个源范围和一个二元谓词,返回一个范围范围,其中每个范围包含源范围中的连续元素,并且满足以下条件:对于该范围中除第一个元素之外的每个元素,当该元素和前一个元素传递给二元谓词时,结果为 true。 本质上,views::chunk_by 将连续元素与二元谓词分组在一起。 -
views::indirect
给定可读值的源范围(例如指针或迭代器),返回一个新视图,该视图是取消引用每个值的结果。 -
views::intersperse
给定源范围和值,返回一个新范围,其中值插入到源中的连续元素之间。 -
views::ints
生成一系列单调递增的整数。 当不带参数使用时,它会生成准无限范围 [0,1,2,3...]。 也可以用下界或用下界和上限(不包括)来调用它。 Closed_ints 提供了包含版本。 -
views::iota
views::ints 的泛化,生成任何可递增类型的单调递增值序列。 当使用单个参数指定时,结果是从指定值开始的无限范围。 对于两个参数,假定这些值表示半开范围。 -
views::join
给定一系列范围,将它们连接成扁平的元素序列。或者,您可以指定要在每个源范围之间插入的值或范围。 -
views::keys
给定一系列对(如 std::map),返回一个仅包含该对的第一个元素的新范围。 -
views::linear_distribute
在闭区间 [from, to] 中线性分布 n 个值(始终包含端点)。如果 from == to,则返回 n 次 to,如果 n == 1,则返回 to。 -
views::move
给定源范围,返回一个新范围,其中每个元素已转换为右值引用。 -
views::partial_sum
给定一个范围和一个二元函数,返回一个新范围,其中第 N 个元素是将函数应用于源范围中的第 N 个元素和结果范围中的第 (N-1) 个元素的结果。 -
views::remove
给定源范围和值,过滤掉那些不等于值的元素。 -
views::remove_if
给定源范围和一元谓词,过滤掉那些不满足谓词的元素。 (对于 Boost.Range 的用户来说,这就像谓词被否定的过滤器适配器。) -
views::repeat
给定一个值,创建一个无限重复该值的范围。 -
views::repeat_n
给定一个值和一个计数,创建一个范围,该范围是该值重复的 count 次。 -
views::replace
给定源范围、源值和目标值,创建一个新范围,其中所有等于源值的元素都将替换为目标值。 -
views::replace
给定源范围、源值和目标值,创建一个新范围,其中所有等于源值的元素都将替换为目标值。 -
views::replace_if
给定源范围、一元谓词和目标值,创建一个新范围,其中满足谓词的所有元素都将替换为目标值。 -
views::reverse
创建一个以相反顺序遍历源范围的新范围。 -
views::sample
返回长度范围 size(range) 的随机样本。 -
views::slice
为源范围指定下限(包含)和上限(不包含),创建一个以指定偏移量开始和结束的新范围。 开始和结束都可以是相对于前面的整数,或者使用“end-2”语法相对于结尾的整数。 -
views::sliding
给定范围和计数 n,在基础范围的前 n 个元素上放置一个窗口。 返回该窗口的内容作为调整范围的第一个元素,然后将窗口一次向前滑动一个元素,直到到达基础范围的末尾。 -
views::split
给定源范围和分隔符说明符,使用分隔符说明符将源范围拆分为一系列范围以查找边界。 分隔符说明符可以是一个元素或一系列元素。 与分隔符匹配的元素将从结果范围中排除。 -
views::split_when
给定源范围和分隔符说明符,使用分隔符说明符将源范围拆分为一系列范围以查找边界。 分隔符说明符可以是谓词或函数。 谓词应采用范围引用类型的单个参数,并当且仅当该元素是分隔符的一部分时返回 true。 该函数应该接受一个迭代器和哨兵,指示当前位置和源范围的结尾,如果当前位置是边界,则返回 std::make_pair(true, iterator_past_the_delimiter) ; 否则 std::make_pair(false,ignored_iterator_value)。 与分隔符匹配的元素将从结果范围中排除。 -
views::stride
给定源范围和整数步长值,返回由第 N 个元素组成的范围(从第一个元素开始)。 -
views::tail
给定源范围,返回不带第一个元素的新范围。该范围必须至少包含一个元素。 -
views::take
给定源范围和整数计数,返回由源范围中的第一个 count 元素组成的范围,如果元素较少,则返回完整范围。 (views::take 的结果不是 sized_range。) -
views::take_exactly
给定源范围和整数计数,返回由源范围中的第一个计数元素组成的范围。源范围必须至少包含那么多元素。 (views::take_exactly 的结果是一个 sized_range。) -
views::take_last
给定源范围和整数计数,返回由源范围中最后计数元素组成的范围。源范围必须是 sized_range。如果源范围没有至少 count 个元素,则返回完整范围。 -
views::take_while
给定源范围和一元谓词,返回一个由前面满足谓词的元素组成的新范围。 -
views::tokenize
给定源范围和可选的子匹配说明符和 std::regex_constants::match_flag_type,返回 std::regex_token_iterator 以逐步遍历源范围的正则表达式子匹配。 子匹配说明符可以是普通 int、std::vector<int> 或 std::initializer_list<int>。 -
views::transform
给定源范围和一元函数,返回一个新范围,其中每个结果元素是将一元函数应用于源元素的结果。 -
views::trim
给定源双向范围和一元谓词,返回一个新范围,不包含满足谓词的前后元素。 -
views::unbounded
给定一个迭代器,返回从该位置开始的无限范围。 -
views::unique
给定一个范围,返回一个新范围,其中除了第一个之外比较相等的所有连续元素都已被过滤掉。 -
views::values
给定一个对的范围(如 std::map),返回一个仅包含该对的第二个元素的新范围。 -
views::zip
给定 N 个范围,返回一个新范围,其中第 M 个元素是对所有 N 个范围的第 M 个元素调用 make_tuple 的结果。 -
views::zip_with
给定 N 个范围和一个 N 元函数,返回一个新范围,其中第 M 个元素是对所有 N 个范围的第 M 个元素调用该函数的结果。
动作(Actions)
动作列表
下面是 Range-v3 提供的立即范围组合器或动作的列表,以及关于如何使用每个组合器的简介。
-
actions::drop
删除源范围的前 N 个元素。 -
actions::drop_while
删除源范围中满足一元谓词的第一个元素。 - actions::erase
删除源子范围(范围版本)中的所有元素或位置之后的所有元素。 - actions::insert
将范围内的所有元素插入源中的位置。 -
actions::join
展平一系列范围。 - actions::push_back
将元素附加到源的尾部。 - actions::push_front
将元素附加到源头之前。 -
actions::remove_if
从源中删除满足谓词的所有元素。 -
actions::remove
从源中删除所有等于 value 的元素。 -
actions::reverse
反转容器中的所有元素。 -
actions::shuffle
随机排列源范围。 -
actions::slice
从源中删除不属于子范围的所有元素。 -
actions::sort
对源范围进行排序(不稳定)。 -
actions::split
使用分隔符(值、值序列、谓词或返回对<bool, N> 的二元函数)将范围拆分为一系列子范围。 -
actions::stable_sort
对源范围进行排序(稳定)。 -
actions::stride
删除位置与步幅不匹配的所有元素。 -
actions::take
保留范围的前 N 个元素,删除其余元素。 -
actions::take_while
保留满足谓词的第一个元素,删除其余元素。 -
actions::transform
用一元函数的结果替换源的元素。 -
actions::unique
删除源中比较相等的相邻元素。如果源已排序,则删除所有重复元素。 -
actions::unstable_remove_if
更快(每个元素删除具有恒定的时间复杂度),remove_if 的无序版本。需要双向容器。
工具
使用 view_facade 创建一个用户视图
使用 view_adaptor 创建一个用户视图
view_adaptor 的细节