**C++20 Ranges:从基础到高级技巧**

C++20 引入了 Ranges 库,为处理容器、迭代器、算法等提供了更直观、更安全、更高效的接口。本文将带你从基础语法开始,逐步探索 Ranges 的高级用法,并给出实战示例,帮助你在项目中更好地利用这项技术。


1. Ranges 基础概念

1.1 Range vs Iterator

  • Iterator:遍历元素的指针/引用,负责“谁来访问”,但不关心“遍历什么”。
  • Range:由起始和结束迭代器组成的可遍历对象,更像是“容器的子集”。

Ranges 将 操作(如 filter, transform, take, drop)与 范围 分离,使得算法可以直接作用于范围而不是单独的迭代器。

1.2 标准 Ranges 头文件

#include <ranges>   // 所有范围相关的视图、操作符
#include <algorithm> // 传统算法

1.3 范围视图(View)

  • view::filter:按谓词过滤元素。
  • view::transform:按函数映射元素。
  • view::take / drop:取前 N 或忽略前 N 个元素。
  • view::reverse:反转迭代顺序。

视图是 惰性 的:真正访问元素时才会执行。


2. 经典示例:过滤与映射

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

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

    auto even_squares = nums
        | std::ranges::views::filter([](int n){ return n % 2 == 0; })
        | std::ranges::views::transform([](int n){ return n * n; });

    for (int x : even_squares) {
        std::cout << x << ' ';
    }
    // 输出: 4 16 36
}
  • filter 先筛选偶数。
  • transform 对每个偶数求平方。
  • 迭代器链式调用,语义清晰。

3. 视图与容器:subrangeiota_view

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

int main() {
    std::vector <char> letters{'a','b','c','d','e','f','g','h'};

    // 取子范围
    auto sub = std::ranges::subrange(letters.begin() + 2, letters.begin() + 6);
    for (char c : sub) std::cout << c; // 输出 "cdef"

    // 生成 1..10
    auto numbers = std::ranges::iota_view(1, 11);
    for (int n : numbers) std::cout << n << ' '; // 1 2 3 ... 10
}

subrange 让你在不拷贝容器的情况下取其一部分。
iota_view 生成连续整数范围,常用于循环。


4. 高级技巧:组合与管道化

4.1 复合视图

auto complex = std::ranges::views::iota(0, 100)
    | std::ranges::views::filter([](int n){ return n % 3 == 0; })
    | std::ranges::views::transform([](int n){ return std::string(n, '*'); })
    | std::ranges::views::reverse;

这段代码生成 0-99 内能被 3 整除的数字,转换为对应数量的星号字符串,最终逆序输出。

4.2 std::ranges::join 与多维容器

std::vector<std::vector<int>> vv{{1,2},{3,4,5},{6}};
auto flattened = std::ranges::views::join(vv);
for (int x : flattened) std::cout << x << ' '; // 1 2 3 4 5 6

join 将二维结构“拉平”,类似 flatten()

4.3 std::ranges::to(C++23)

在 C++23 中可直接将视图转换为容器:

auto vec = std::ranges::to<std::vector>(even_squares);

此功能可在 C++20 中通过自定义 to_container 实现。


5. 性能与懒加载

  • 惰性求值:视图不会在定义时执行,而是在遍历时执行,减少不必要的计算。
  • 按需迭代:一次遍历即可完成过滤与映射,减少临时容器。
  • 编译期推导:视图的类型完全由编译器推导,运行时无额外开销。

实践建议:对大数据集使用视图链式操作;若需要多次遍历,请先 materialize(转为容器)或使用 std::ranges::to.


6. 与旧有 STL 的互操作

std::vector <int> v{1,2,3,4,5};
auto v_view = std::ranges::views::all(v);  // 将容器包装为范围

auto sum = std::accumulate(v_view.begin(), v_view.end(), 0);
// 等价于 std::accumulate(v.begin(), v.end(), 0);

views::all 可将任何容器或迭代器对接为 Range,保持兼容性。


7. 常见陷阱

误区 说明
直接对 std::vector 使用 | views::filter vector 本身不是 Range,需要 views::all 包装
忘记 std::ranges::views::common 某些视图的迭代器不是完整范围,需要 common 强制使其成为完整范围
误用 take(0) 返回空范围,注意避免后续对空范围的操作导致异常

8. 小结

  • Ranges 让算法与容器解耦,语义更清晰。
  • 视图是惰性、组合性强的工具,能显著减少临时对象与复制。
  • 在 C++20 项目中加入 Ranges 可提升代码可读性与性能。

接下来,你可以尝试将现有项目中的 for 循环、std::copy_if 等改写为 Ranges,感受不同的编码体验。祝你编码愉快!

发表评论