1+1=10

记记笔记,放松一下...

C++ Ranges小记

什么是范围(Ranges)?

Ranges 是 C++20 提供的一种更现代的处理集合的方式,可以看作是对迭代器的进一步抽象。

如果要筛选处vector中的偶数,可使用ranges:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <vector>
#include <iostream>
#include <ranges>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6};

    for (int n : v | std::views::filter([](int n){ return n % 2 == 0; })) {
        std::cout << n << " ";
    }
    return 0;
}

其中,std::views::filter 是一个 Range 适配器,它直接对Range v 进行过滤操作。通过管道操作符 |,我们将过滤器应用到range上。

在 C++20 之前,使用标准库算法时,需要显式地传递迭代器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6};

    std::vector<int> result;
    std::copy_if(v.begin(), v.end(), std::back_inserter(result), [](int n){ return n % 2 == 0; });

    for (int n : result) {
        std::cout << n << " ";
    }
    return 0;
}

在这个例子中,使用了 std::copy_if 来过滤 v 中的偶数,需要手动传递起始和结束迭代器。

Ranges 的核心组件

主要由以下三个核心组件:

  • Range 表达式:它可以是一个范围(如容器)或任何可以产生范围的对象。
  • Range 适配器(Views):允许对范围进行变换、过滤等操作。
  • Range 算法:它是标准库中已有算法的扩展版本,直接作用于 Ranges。

Range 表达式

Range 表达式可以是任何可以被视作“范围”的对象,比如标准库容器std::vector、std::list 等。此外,C++20 还引入了 std::ranges::range 概念(concept),用于判定一个类型是否是range。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <vector>
#include <iostream>
#include <ranges>

int main() {
    std::vector<int> v = {1, 2, 3};

    if constexpr (std::ranges::range<decltype(v)>) {
        std::cout << "v is a range" << std::endl;
    }
    return 0;
}

Range 适配器(Views)

Range 适配器(Views)是 Ranges 的关键部分,它们允许对范围进行变换、过滤等操作,而不会修改原始数据。一些的适配器:

  • std::views::filter:过滤满足条件的元素。
  • std::views::transform:对每个元素进行某种变换。
  • std::views::take:获取前 N 个元素。
  • std::views::drop:跳过前 N 个元素。
  • ...

下面例子通过管道操作符 | 组合多个适配器——首先过滤出偶数,然后对这些偶数进行平方操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <vector>
#include <iostream>
#include <ranges>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6};

    for (int n : v | std::views::filter([](int n){ return n % 2 == 0; })
                   | std::views::transform([](int n){ return n * n; })) {
        std::cout << n << " ";
    }
    return 0;
}

Range 算法

C++20 为标准库的算法添加了 Ranges 版本,这些算法直接作用于范围,而不再需要显式传递迭代器。常见的 Range 算法包括:

  • std::ranges::find
  • std::ranges::sort
  • std::ranges::for_each
  • std::ranges::copy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <vector>
#include <iostream>
#include <ranges>

int main() {
    std::vector<int> v = {5, 3, 1, 4, 2};
    std::ranges::sort(v);

    for (int n : v) {
        std::cout << n << " ";
    }
    return 0;
}

std::ranges::sort 直接作用于范围 v,无需显式传递 begin() 和 end() 迭代器。

Range 适配器的惰性计算

Ranges 的一个重要特性是 惰性计算。适配器不会立即执行操作,而是会在真正访问元素时才进行计算。

如下代码输出 0 1 2 3 4 5 6 7 8 9

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <iostream>
#include <ranges>

int main() {
    auto r = std::views::iota(0) | std::views::take(10);

    for (int n : r) {
        std::cout << n << " ";
    }
    return 0;
}

例子中, std::views::iota(0) 生成了一个从 0 开始的无限整数序列,通过 std::views::take(10),我们只取了前 10 个元素。由于 Ranges 的惰性特性,程序并不会生成整个无限序列,而是按需生成并处理元素。

自定义 Range 适配器

C++20 允许我们定义自己的 Range 适配器。自定义适配器可以帮助我们实现特定的数据处理逻辑。下面是一个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <ranges>
#include <iostream>
#include <concepts>

struct double_transform_t {
    template<std::ranges::input_range R>
    constexpr auto operator()(R&& r) const {
        return std::views::transform(r, [](auto x) { return x * 2; });
    }

    friend constexpr auto operator|
        (std::ranges::input_range auto&& r, double_transform_t d) { return d(r); }
} inline constexpr double_transform;

int main() {
    auto r = std::views::iota(1) | std::views::take(5) | double_transform;

    for (int i : r)
        std::cout << i << " "; // output:2 4 6 8 10
    std::cout << '\n';

    return 0;
}

在这个例子中,double_transform 是一个简单的自定义 Range 适配器,它将范围中的每个元素都乘以 2。

尽管 view_base是空类,据说从它派生会有好多好处:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct double_transform_t : std::ranges::view_base {
    template<std::ranges::input_range R>
    constexpr auto operator()(R&& r) const {
        return std::views::transform(r, [](auto x) { return x * 2; });
    }
} inline constexpr double_transform;

template<std::ranges::input_range R>
constexpr auto operator|(R&& r, double_transform_t) {
    return double_transform(r);
}

参考

  • https://zh.cppreference.com/w/cpp/ranges

C++