C++20 引入了 Ranges 库(位于 <ranges> 头文件中),这是对标准模板库 (STL) 的重大扩展和泛化。它使得迭代器和算法更强大、更易用,主要通过引入范围 (range) 的概念来实现统一处理各种数据结构(如数组、向量、列表等)。Ranges 库的核心优势在于算法的懒惰求值 (lazy evaluation)、直接操作容器,以及可组合性 (composability),这大大简化了代码编写,避免了传统 STL 中常见的迭代器对 (begin/end) 显式使用。

std::ranges 关键概念

  • Range:一个范围是一个可迭代的序列,必须提供 begin()end()(哨兵)。示例包括 std::vectorstd::arraystd::string_view 等,甚至 C 风格数组。
  • Views:视图是范围的轻量级、懒惰表示,不会复制数据,只在需要时计算。它们是 Ranges 库的核心,用于管道式组合(如使用 | 操作符)。
  • Adaptors:如 views::filterviews::transformviews::take 等,用于修改范围而不改变底层数据。
  • Algorithms:Ranges 版本的算法(如 std::ranges::sortstd::ranges::find)直接接受范围作为参数,支持投影 (projections) 来简化操作特定成员。
  • Actions:类似于算法,但会修改容器(如 ranges::sort 会就地排序)。

Ranges 库的算法是懒惰的:它们不会立即执行,而是返回视图,直到被消费(如迭代或转换为容器)。这提高了效率,尤其在处理大数据集时。

案例1:基本过滤和转换

假设我们有一个整数向量,需要过滤偶数并将其平方。

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

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6};
    
    auto even_squares = nums 
        | std::views::filter([](int n) { return n % 2 == 0; }) 
        | std::views::transform([](int n) { return n * n; });
    
    for (int sq : even_squares) {
        std::cout << sq << " ";  // 输出: 4 16 36
    }
}

这里,使用管道 | 组合了 filtertransform 视图。计算是懒惰的,只有在循环中才执行。

案例2:排序和投影

对一个结构体向量按成员排序,使用投影。

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

struct Person {
    std::string name;
    int age;
};

int main() {
    std::vector<Person> people = {
        {"Alice", 30}, 
        {"Bob", 25}, 
        {"Charlie", 35}};
    
    // 按年龄排序,使用投影
    std::ranges::sort(people, {}, &Person::age);
    
    for (const auto& p : people) {
        // 输出: Bob (25) Alice (30) Charlie (35)
        std::cout << p.name << " (" << p.age << ") ";
    }
}

投影 &Person::age 避免了编写完整 lambda,提高了可读性。

案例3:读取流数据

从输入流读取单词并处理。

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

int main() {
    std::string input = "Hello world from C++ ranges";
    std::istringstream iss(input);
    
    auto words = std::views::istream<std::string>(iss) 
        | std::views::transform([](const std::string& s) { 
                return s.size(); 
            });
    
    for (auto len : words) {
        // 输出: 5 5 4 3 6
        std::cout << len << " ";
    }
}

使用 views::istream 将流转换为范围,结合 transform 计算单词长度。

C++23 的 std::ranges::to

C++23 扩展了 Ranges 库,引入了 std::ranges::to 函数,这是一个便利工具,用于从范围或视图急切地 (eagerly) 构造一个新容器。它接受一个模板参数指定目标容器类型,并自动推导元素类型。to 会强制评估视图,将结果存储到容器中,支持自定义分配器等高级用法。

语法示例:auto container = range | views::... | std::ranges::to<std::vector>();

这解决了 C++20 中需要手动使用 std::ranges::copy 或循环来收集结果的问题,使代码更简洁。

案例4:使用 ranges::to 收集过滤结果

从向量过滤偶数并转换为新向量。

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

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6};
    
    auto evens = nums 
        | std::views::filter([](int n) { return n % 2 == 0; }) 
        | std::ranges::to<std::vector>();
    
    for (int n : evens) {
        std::cout << n << " ";  // 输出: 2 4 6
    }
}

to<std::vector>() 直接创建新容器。

案例5:结合 zip 和 to 处理多个范围

使用 C++23 的 views::zip 结合两个向量,并转换为 map。

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

int main() {
    std::vector<std::string> keys = {"a", "b", "c"};
    std::vector<int> values = {1, 2, 3};
    
    auto zipped = std::views::zip(keys, values) 
        | std::ranges::to<std::map>();
    
    for (const auto& [k, v] : zipped) {
        // 输出: a: 1 b: 2 c: 3
        std::cout << k << ": " << v << " ";
    }
}

zip 结合范围,to<std::map>() 自动推导键值对。

案例6:从生成器收集数据

使用 C++23 的 std::generator 生成序列,并用 to 收集。

#include <iostream>
#include <vector>
#include <ranges>
#include <generator>  // C++23

std::generator<int> fib_gen(int n) {
    int a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;
        std::tie(a, b) = std::make_tuple(b, a + b);
    }
}

int main() {
    auto fibs = fib_gen(5) | std::ranges::to<std::vector>();
    
    for (int f : fibs) {
        // 输出: 0 1 1 2 3
        std::cout << f << " ";
    }
}

generator 产生斐波那契数,to 将其转换为向量。