std::ranges::views::chunk_by是按相邻元素二元谓词结果翻转动态切分序列的视图,而std::ranges::views::chunk是按固定大小位置驱动切分;前者返回subrange序列,后者返回等长块,二者语义完全不兼容。

chunk_by 是什么,它和 chunk 有什么区别
std::ranges::views::chunk_by 不是按固定长度切分,而是根据相邻元素是否满足某个二元谓词来动态划分逻辑组。它和 std::ranges::views::chunk 完全不同:chunk 按 size 切(比如每 3 个一组),而 chunk_by 看“断点”——当 pred(*it, *(it+1)) 为 false 时,就在此处切一刀。
典型场景:把连续相同值的子序列归为一组(类似 Python 的 itertools.groupby),或按单调性变化分段(如升序段、降序段)。
- 必须传入一个二元谓词(
std::binary_predicate),不能省略 - 输入范围必须是 forward_range,且至少支持
++it和*it - 返回的是 view,不拷贝数据;每个 chunk 是一个
subrange,需用std::ranges::begin/end或范围 for 访问
怎么写谓词才能正确分组连续相等元素
最常见需求是“把 [1,1,2,2,2,3] 分成 {1,1}, {2,2,2}, {3}”,此时谓词应返回 true 表示“属于同一组”,即:当前元素和下一个元素“相容”,不应在此处分割。
所以判断连续相等,要写成 [=](const auto& a, const auto& b) { return a == b; },而不是 a != b。写反会导致每组只含 1 个元素。
立即学习“C++免费学习笔记(深入)”;
std::vector<int> v = {1,1,2,2,2,3};
auto groups = v | std::ranges::views::chunk_by([](int x, int y) { return x == y; });
for (const auto& chunk : groups) {
std::cout << "group: ";
for (int x : chunk) std::cout << x << ' ';
std::cout << '\n';
}
输出:
group: 1 1
group: 2 2 2
group: 3
chunk_by 在 vector vs list 上的行为差异
chunk_by 内部需要对迭代器做自增和解引用操作,因此要求底层容器支持 forward iteration。这在 std::vector 和 std::list 上都满足,但性能表现不同:
-
std::vector:随机访问快,chunk_by 迭代高效;各 chunk 的begin()/end()是普通指针或随机访问迭代器 -
std::list:每次++it是 O(1),但遍历仍比 vector 慢;各 chunk 的迭代器是双向的,不能直接算距离 - ⚠️
std::forward_list不行:它不支持std::ranges::size,且chunk_by内部可能隐式依赖多遍遍历能力(标准未强制要求 single-pass),实践中多数实现会拒绝编译
常见编译错误和坑
最常遇到的错误是谓词签名不匹配或范围不可见:
-
error: no matching function for call to 'chunk_by':多半因为没引入<ranges>,或用了 C++20 之前的编译模式(需-std=c++20) -
error: use of deleted function 'operator=':试图把chunk_byview 赋给std::vector<auto>—— view 不可直接构造容器,要用std::ranges::to<std::vector>或手动拷贝 - 谓词捕获外部变量但未声明
mutable:如果谓词里修改了捕获变量(比如计数器),lambda 必须加mutable,否则编译失败 - 空 range 输入:
chunk_by对空 range 返回空 view,安全,但别假设至少有一个 chunk
真正麻烦的是谓词逻辑边界——比如按大小分组时写成 x < y,那 [1,3,2] 会被切成 [1,3], [2],但 [1,2,3] 就变成一整组。这种语义必须和业务目标严格对齐,没法靠试错解决。


















