# C++23 新特性:ranges-v3 与 std::ranges 的对比与实践

1. 背景介绍

在 C++17 之后,标准库的 std::ranges 提供了基于范围(range)的视图(view)和算法(algorithm),大大提升了序列操作的表达力和效率。2019 年,提议将 ranges-v3(一个第三方库,C++20 之前的 ranges 实现)合并到标准库的路线图中。如今 C++23 已经把 ranges-v3 的核心功能正式纳入标准库,部分实现细节与 ranges-v3 仍有差异。本文以对比两者的实现细节、语义差异和实际使用场景为主,帮助读者在项目中做出更合适的选择。

2. 基本概念回顾

术语 含义
View 一个轻量级、懒惰求值的容器,能在不复制元素的情况下对底层数据进行转换。
Pipe 类似函数式编程中的管道,用 | 运算符把视图组合成更复杂的表达式。
Iterator 对元素的访问方式,view 本身并不存储元素,只是提供对原始容器的迭代器。

3. ranges-v3 的核心特性

  • 可组合的视图:如 filter, transform, take, drop, join 等。
  • 高性能:所有视图都是 懒惰 的,只有在迭代时才会执行转换。
  • 兼容 C++14/17:不需要更新编译器就能使用。
#include <range/v3/all.hpp>
using namespace ranges;

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

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

    for (int val : even_squares)
        std::cout << val << ' ';
}

4. C++23 std::ranges 的实现差异

4.1 命名空间与头文件

  • ranges-v3#include <range/v3/all.hpp>,使用 namespace ranges
  • C++23:`#include `,使用 `namespace std::ranges`。

4.2 视图的构造方式

ranges-v3 通过模板参数 auto 捕获函数对象;C++23 在 std::ranges::views 中采用了 管道(pipe)与 通用视图(generic view)组合方式,并在 23 中引入了 std::ranges::views::all_t 以统一容器与视图。

// ranges-v3
auto squares = nums | views::transform([](int n){ return n * n; });

// std::ranges
auto squares = std::views::transform(nums, [](int n){ return n * n; });

4.3 std::ranges::viewranges::view

  • ranges::view 是概念(concept),view 是一个包装类;
  • std::ranges::view 也是概念,但其实现细节更紧凑,支持 viewable_range(可视化范围)。

4.4 迭代器类别

ranges-v3 中,views::filter 的迭代器是 filter_view;在 std::ranges 中,迭代器采用 std::ranges::filter_view,但提供了更细粒度的 sentineliterator 区分。

4.5 性能对比

  • 编译时间std::ranges 的模板更简洁,编译器优化空间更大。
  • 运行时:两者都实现了 lazy 计算,性能相差不大;但在极端情况下,std::rangesviews::iotaviews::zip 对内存布局做了更好的优化。

5. 典型使用场景

场景 适用库 说明
需要支持 C++14/17 项目 ranges-v3 由于标准库尚未支持,必须使用第三方库。
需要使用 std::ranges::views::zip C++23 该视图在标准库实现中更轻量。
需要在编译阶段对代码进行重构(如 refactoring) ranges-v3 由于 ranges-v3 提供了更丰富的工具,例如 views::transform 的通用性更高。
需要与现有标准库算法(std::for_each, std::accumulate)配合 std::ranges 通过 std::ranges::subrange 可以直接与算法配合。

6. 代码实例:C++23 的 views::zipranges-v3views::zip 对比

// ranges-v3
#include <range/v3/all.hpp>
using namespace ranges;
int main() {
    std::vector <int> a{1,2,3};
    std::vector <char> b{'a','b','c','d'};
    for (auto&& [x, y] : a | views::zip(b))
        std::cout << x << '-' << y << ' ';
}

// std::ranges (C++23)
#include <ranges>
int main() {
    std::vector <int> a{1,2,3};
    std::vector <char> b{'a','b','c','d'};
    for (auto&& [x, y] : std::views::zip(a, b))
        std::cout << x << '-' << y << ' ';
}

两段代码功能完全相同,区别在于 std::views::zip 的返回类型在编译期更加准确,且对 std::arraystd::span 等新容器具有更好的兼容性。

7. 结论与建议

  1. 如果项目已经使用 C++23 或更高,建议直接使用 std::ranges,其 API 与 ranges-v3 十分相似,但更符合标准规范,未来的维护成本更低。
  2. 如果项目受限于 C++14/17,或者需要利用 ranges-v3std::ranges 之前出现的成熟生态(如 views::join, views::common 等),ranges-v3 仍然是优选。
  3. 混用:在 C++23 项目中可以同时包含 ranges-v3,但需要注意命名冲突,推荐使用 using namespace ranges 并显式使用 std::ranges::views 来区分。
  4. 性能:两者几乎没有显著差异,主要关注编译时间与可读性。

通过上述对比,开发者可以根据项目的语言版本、已有代码以及对性能的细节要求,选择最合适的 ranges 实现。祝编码愉快!

发表评论