<ranges>

概括而言,范围是可以迭代的某种属性。 范围由迭代器(标记范围的开头)和 sentinel(标记范围的末尾)表示。 sentinel 的类型可能与开始迭代器相同,也可能不同。 C++ 标准库中的容器(例如,vectorlist)是范围。 范围以一种简化和增强标准模板库 (STL) 使用能力的方式来抽象迭代器。

STL 算法通常采用指向其应该操作的集合部分的迭代器。 例如,请考虑如何使用std::sort()vector进行排序。 传递两个迭代器来标记vector的开头和末尾。 这可以获得灵活性,但将迭代器传递给算法会造成额外的工作,因为你可能只想对整个对象进行排序。

使用范围,可以调用std::ranges::sort(myVector);,这就相当于调用了std::sort(myVector.begin(), myVector.end());。 在范围库中,算法将范围用作参数(不过,如果需要的话,它们也可以使用迭代器)。 它们可以直接对集合进行操作。 <algorithm>中可用的范围算法示例包括copycopy_ncopy_ifall_ofany_ofnone_offindfind_iffind_if_notcountcount_iffor_eachfor_each_nequalmismatch

但也许范围最重要的好处是,可以编写 STL 算法,这些算法以让人想起函数编程的样式对范围进行操作。

范围示例

在范围问世之前,如果想转换集合中满足特定条件的元素,需要引入中间步骤以保存运算之间的结果。 例如,如果想从另一个可被三整除的向量中的元素生成平方向量,可以编写如下内容:

std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::vector<int> intermediate, output;

std::copy_if(input.begin(), input.end(), std::back_inserter(intermediate), [](const int i) { return i%3 == 0; });
std::transform(intermediate.begin(), intermediate.end(), std::back_inserter(output), [](const int i) {return i*i; });

使用范围,可以在不需要 intermediate 向量的情况下达到相同的目的:

// requires /std:c++20
std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

auto output = input
    | std::views::filter([](const int n) {return n % 3 == 0; })
    | std::views::transform([](const int n) {return n * n; });

除了更易于阅读之外,此代码还避免了intermediate向量及其内容所需的内存分配。 它还允许组合两个操作。

在前面的代码中,每个可被 3 整除的元素都与对该元素求平方的运算相结合。 管道 (|) 符号将运算链接在一起,从左到右读取。

结果output本身是称为视图的范围。

视图

视图是一个轻量范围。 例如默认构造、移动构造/赋值、复制构造/赋值(如果存在)、销毁、开始和结束等视图操作都在恒定时间内发生,无论视图中有多少元素。

视图由范围适配器创建,下一部分将予以介绍。 有关实现各种视图的类的详细信息,请参阅视图类

视图中元素的显示方式取决于用于创建该视图的范围适配器。 在以上示例中,范围适配器采用范围并返回可被 3 整除的元素的视图。 基础范围保持不变。

视图可组合,这一点很强大。 在以上示例中,可被 3 整除的向量元素的视图与对这些元素求平方的视图合并。

视图的元素是惰性计算的。 也就是说,在请求元素之前,不会计算应用于视图中每个元素的变换。 例如,如果在调试器中运行以下代码并在行 auto divisible_by_three = ...auto square = ... 上放置一个断点,则会命中 divisible_by_three lambda 断点,因为 input 中的每个元素经测试可被 3 整除。 将命中 square lambda 断点,因为可被 3 整除的元素已求平方。

// requires /std:c++20
#include <ranges>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> input =  { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    auto divisible_by_three = [](const int n) {return n % 3 == 0; };
    auto square = [](const int n) {return n * n; };

    auto x = input | std::views::filter(divisible_by_three)
                   | std::views::transform(square);

    for (int i : x)
    {
        std::cout << i << '\n';
    }
    return 0;
}

有关视图的详细信息,请参阅<ranges> 视图类

范围适配器

范围适配器采用范围并生成视图。 范围适配器生成惰性计算的视图。 也就是说,不会产生变换范围内每个元素以生成视图的相关开销。 访问该元素时,只需支付在视图中处理元素的成本。

在以上示例中,filter范围适配器创建名为input的视图,其中包含可被 3 整除的元素。 transform范围适配器采用可被 3 整除的元素的视图,并创建这些元素的平方视图。

范围适配器可以链接在一起(组合),这是范围的强大和灵活性的核心。 组合范围适配器可以克服不容易组合的旧 STL 算法的问题。

有关创建视图的详细信息,请参阅范围适配器

范围算法

一些范围算法采用范围参数。 示例为 std::ranges::sort(myVector);

范围算法与std命名空间中的相应迭代器对算法几乎完全相同。 区别在于,它们具有概念强制约束,它们接受范围参数或更多迭代器-sentinel 参数对。 它们可以直接针对容器运行,并可以轻松链接在一起。

<ranges> 函数

以下函数用于为范围创建迭代器和 sentinel,并获取范围大小。

函数 说明
beginC++20 获取指向范围中第一个元素的迭代器。
cbeginC++20 获取指向范围中第一个元素的 const 迭代器。
cendC++20 获取 const 选定的范围末尾的 sentinel。
cdataC++20 获取指向连续范围中第一个元素的 const 指针。
crbeginC++20 获取指向范围开头的反向 const 迭代器。
crendC++20 获取 crbegin() 返回结果末尾的 sentinel。
dataC++20 获取指向连续范围中第一个元素的指针。
emptyC++20 确定范围是否为空。
endC++20 获取范围末尾的 sentinel。
rbeginC++20 获取指向范围开头的反向迭代器。
rendC++20 获取指向范围末尾的 sentinel 的反向迭代器。
sizeC++20 获取范围的大小作为无符号值。
ssizeC++20 获取范围的大小作为带符号值。

有关详细信息,请参阅 <ranges> 函数

范围概念

循环访问范围元素的方式取决于其基础迭代器类型。 范围使用 C++ 概念指定它们支持的迭代器。

在 C++20 中,假设概念X细化概念Y意味着满足概念Y的所有内容也满足概念X。例如:轿车公共汽车卡车都细化车辆

一些范围概念反映了迭代器类别的层次结构。 下表列出了范围概念,以及它们可以应用到的容器类型。

范围概念 说明 支持的容器
std::ranges::output_range 可以向前迭代。
std::ranges::input_range 至少可以从头到尾迭代一次。 std::forward_list
std::unordered_map
std::unordered_multimap
std::unordered_set
std::unordered_multiset
basic_istream_view
std::ranges::forward_range 可以从头到尾迭代多次。 std::forward_list
std::unordered_map
std::unordered_multimap
std::unordered_set
std::unordered_multiset
std::ranges::bidirectional_range 可以多次向前和向后迭代。 std::list
std::map
std::multimap
std::multiset
std::set
std::ranges::random_access_range 可以使用[]运算符访问任意元素(在恒定时间内)。 std::deque
std::ranges::contiguous_range 元素按顺序存储在内存中。 std::array
std::string
std::vector

有关这些概念的详细信息,请参阅<ranges> 概念

<ranges> 别名模板

以下别名模板确定了范围的迭代器和 sentinel 类型:

别名模板 说明
borrowed_iterator_tC++20 确定为 range 返回的迭代器是否引用生存期已结束的范围。
borrowed_subrange_tC++20 确定为subrange返回的迭代器是否引用生存期已结束的子范围。
danglingC++20 指示返回的 range/subrange 迭代器大于所引用 range/subrange 的生存期。
iterator_tC++20 返回指定的范围类型的迭代器类型。
range_difference_tC++20 返回指定范围的迭代器类型的差异类型。
range_reference_tC++20 返回指定范围的迭代器类型的引用类型。
range_rvalue_reference_tC++20 返回指定范围的迭代器类型的 rvalue 引用类型。 换句话说,范围元素的 rvalue 引用类型。
range_size_tC++20 返回用于报告指定范围大小的类型。
range_value_tC++20 返回指定范围的迭代器类型的值类型。 或者换句话说,范围中元素的类型。
sentinel_tC++20 返回指定范围的 sentinel 类型。

有关这些别名模板的详细信息,请参阅<ranges> 别名模板

另请参阅

<ranges> 函数
<ranges> 概念
范围适配器
头文件引用