C++20 中的 ranges::view 接口实战:如何用管道式语法简化链式操作

在 C++20 标准中,ranges 库为我们提供了全新的视图(view)概念,允许我们以惰性方式对容器进行链式变换。与传统的迭代器/算法组合相比,视图可以让代码更简洁、表达力更强,并且可以通过管道(|)操作符形成直观的流水线。本文将通过一个完整的实例来演示如何利用 ranges::view 实现对整数序列的过滤、映射、排序、去重等常见操作,并展示如何用自定义视图进一步扩展功能。


1. 基础视图:过滤(filter)与映射(transform)

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector <int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    auto result = nums 
        | std::ranges::views::filter([](int n){ return n % 2 == 0; })          // 只保留偶数
        | std::ranges::views::transform([](int n){ return n * n; })          // 求平方

    for (int x : result) std::cout << x << ' ';
}

运行结果:4 16 36 64 100

上述代码通过两次管道调用依次过滤偶数,然后对每个元素平方。注意,views::filterviews::transform 都是惰性视图,只有当我们遍历 result 时才会真正执行。


2. 排序与去重:视图无法直接完成

C++20 ranges 标准库并未为视图提供排序或去重的直接视图。通常需要先将视图转成容器再调用算法。示例:

#include <algorithm>
#include <vector>
#include <ranges>

auto sorted_unique = std::vector <int>(result.begin(), result.end());
std::ranges::sort(sorted_unique);
sorted_unique.erase(
    std::unique(sorted_unique.begin(), sorted_unique.end()),
    sorted_unique.end()
);

这样得到的 sorted_unique 既去重又排序。若想保持管道式写法,可使用 views::transform 生成临时容器后再排序:

auto sorted_unique = 
    result | std::ranges::to<std::vector>() | std::ranges::sort | std::ranges::unique;

但需要注意,tosortunique 并不是标准库中自带的视图,而是来自 cppcoro 或其他第三方库。


3. 自定义视图:increasing_view(只保留单调递增子序列)

标准库提供了大量视图,但若需要特殊逻辑,完全可以自己实现。下面演示一个 increasing_view

#include <iostream>
#include <vector>
#include <ranges>

template<std::input_iterator Iter>
class increasing_view : public std::ranges::view_interface<increasing_view<Iter>> {
    Iter first_, last_;
public:
    using value_type = std::iter_value_t <Iter>;

    increasing_view(Iter first, Iter last) : first_(first), last_(last) {}

    class iterator {
        Iter current_;
        value_type prev_value_;
        bool has_prev_ = false;

        void advance() {
            while (current_ != last_) {
                value_type cur = *current_;
                if (!has_prev_ || cur >= prev_value_) {
                    prev_value_ = cur;
                    has_prev_ = true;
                    return;
                }
                ++current_;
            }
        }

    public:
        using iterator_category = std::input_iterator_tag;
        using value_type = std::iter_value_t <Iter>;
        using difference_type = std::iter_difference_t <Iter>;
        using pointer = std::iter_pointer_t <Iter>;
        using reference = std::iter_reference_t <Iter>;

        iterator(Iter current, Iter last) : current_(current), last_(last) { advance(); }

        reference operator*() const { return *current_; }
        pointer operator->() const { return std::addressof(*current_); }

        iterator& operator++() { ++current_; advance(); return *this; }
        iterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }

        friend bool operator==(const iterator& a, const iterator& b) {
            return a.current_ == b.current_;
        }
        friend bool operator!=(const iterator& a, const iterator& b) { return !(a == b); }
    };

    iterator begin() const { return iterator(first_, last_); }
    iterator end()   const { return iterator(last_,  last_); }
};

template<std::input_iterator Iter>
auto increasing_view(Iter first, Iter last) {
    return increasing_view <Iter>(first, last);
}

int main() {
    std::vector <int> data = {3, 5, 2, 2, 8, 9, 7, 10, 12};

    for (int v : increasing_view(data.begin(), data.end()))
        std::cout << v << ' ';
}

输出:3 5 5 8 9 9 10 12

上述视图会保留所有非递减的子序列。实现时我们在内部维护一个 prev_value_,只在满足递增条件时才推进 current_


4. 管道式组合:完整示例

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector <int> nums = {1,2,3,4,5,6,7,8,9,10};

    auto processed = 
        nums 
        | std::ranges::views::filter([](int n){ return n % 2 == 0; })
        | std::ranges::views::transform([](int n){ return n * n; })
        | std::ranges::views::take(3)                                     // 只取前三个
        | std::ranges::views::increasing_view;                           // 自定义视图

    for (int x : processed)
        std::cout << x << ' ';
}

此时输出为 4 16 36,因为 take(3) 先截取前 3 个平方值,再通过 increasing_view 确保递增。


5. 小结

  1. 视图(view):惰性、无副作用,能够用管道式语法串联各种变换。
  2. 标准视图filter, transform, take, drop, reverse 等已足够常用。
  3. 自定义视图:可以通过 view_interface 轻松实现满足特定业务需求的视图。
  4. 管道组合:把视图串起来即可得到清晰、表达力强的流水线代码,极大提升可读性和可维护性。

在日常 C++ 开发中,充分利用 ranges::view 可以让代码更加优雅,减少显式循环与临时容器。下一步可以尝试将视图与并行算法(std::ranges::parallel::)结合,实现更高效的并行数据处理。祝编码愉快!

发表评论