在 C++ 23 标准中, 库得到了显著扩展,其中最引人注目的是对计数器(std::ranges::counting_view)的改进。之前的实现已经提供了一个轻量级的视图,用于生成从起始点到终点的递增序列,但在性能、可定制性以及与其他视图的组合方面仍有提升空间。C++ 23 的更新使得计数器视图在多种场景下更具可用性。
1. 计数器视图的核心改进
1.1 更细粒度的步长控制
旧版的 counting_view 只支持步长为 1 的整数序列。现在,它允许用户指定任何可复制且满足 std::integral 要求的步长类型,例如 int8_t、int64_t,甚至是自定义的整数包装器。函数签名如下:
template<std::integral T = std::size_t>
auto counting_view(T first, T last, T step = 1);
这意味着可以更高效地处理大范围或稀疏计数,例如生成 1000 到 2000 之间每隔 5 的整数。
1.2 与 std::ranges::views::filter 的更好兼容
计数器视图现在返回一个具有可迭代特性且支持 begin() 和 end() 的轻量级对象,可以直接与 std::ranges::views::filter、std::ranges::views::transform 等视图链式组合,而无需先转换为容器。例如:
auto evens = std::ranges::views::counting(0, 100, 1)
| std::ranges::views::filter([](int x){ return x % 2 == 0; });
for (int n : evens) std::cout << n << ' ';
这种组合在编译时即完成,避免了中间容器的开销。
1.3 对 std::ranges::range 协议的完整支持
C++ 23 的计数器视图实现了 std::ranges::range 协议,允许在算法的 begin、end 位置直接使用。它还实现了 std::ranges::sentinel 的概念,使得在 for 循环和 std::ranges::for_each 等算法中更安全、更高效。
2. 性能收益
在对比旧版计数器与新版计数器的基准测试时,主要收益体现在:
- 无额外内存分配:计数器始终保持为一个小的结构体,不需要分配内部容器。
- 更低的指针间接:步长的存储直接在结构体中,减少了额外的访问层级。
- 更快的迭代速度:通过对齐和内联优化,编译器能够生成更高效的循环代码,尤其在与
std::ranges::views::transform组合时。
3. 典型使用案例
3.1 生成斐波那契序列
尽管计数器本身不支持斐波那契,但结合 views::transform 可以轻松实现:
auto fib_seq = std::ranges::views::iota(0, 10)
| std::ranges::views::transform([a = 0, b = 1](int){
int c = a + b;
a = b;
b = c;
return a;
});
for (auto f : fib_seq) std::cout << f << ' ';
3.2 计算大范围的素数
利用计数器与 views::filter 组合,并配合 std::ranges::cpp20::is_prime(假设存在),可以快速过滤出素数:
auto primes = std::ranges::views::counting(2, 1000000, 1)
| std::ranges::views::filter(is_prime);
std::cout << "素数个数: " << std::ranges::distance(primes) << '\n';
4. 与现有代码的兼容性
计数器视图的 API 兼容旧版,并且通过 std::ranges::views::counting 进行访问。旧代码仍可在新标准下编译,且无需做大规模改动。建议在项目中逐步迁移到新的视图链式写法,以获得更高的可读性与性能。
5. 小结
C++ 23 对 std::ranges::counting_view 的改进,使其在步长自定义、视图组合、范围协议支持以及性能上都获得显著提升。对于需要在编译时生成数值序列的场景,新的计数器视图提供了更灵活、更高效的工具,值得在日常编码实践中尝试。