std::views::chunk_by不能按固定长度分组,它仅根据相邻元素间谓词结果的变化切分;正确用法是二元、无状态、类型匹配的谓词,如按首字母或十位分组。

想用 std::views::chunk_by 把容器按固定长度(比如每 3 个一组)切分?不行。它根本不接受整数参数,也不看索引或计数,只响应相邻元素间谓词结果的**变化**。
为什么 std::views::chunk_by(std::equal_to{}) 不等于“按值分组”
它不是把所有相等的元素归到一块,而是仅在 a 和 b(相邻)传入谓词后结果翻转时才切——即上一对返回 true、下一对返回 false,或反之。例如对 {1,2,1,1}:
-
pred(1,2)→false→ 新块起始([1]) -
pred(2,1)→false→ 继续新块([2]) -
pred(1,1)→true→ 仍属当前块([1,1])
最终是 [1]、[2]、[1,1],而非你直觉中“所有 1 归为一组”的结果。误以为它能替代 std::views::split 或全局去重逻辑,是常见起点错误。
std::views::chunk_by 的谓词签名和状态约束
必须是二元、无状态、确定性可调用对象,形如 bool(const T&, const T&)。以下写法全部非法:
立即学习“C++免费学习笔记(深入)”;
[count](auto, auto) mutable { return ++count —— 捕获并修改局部变量-
[](auto a, auto b) { static int x; return a > x++; }—— 静态变量引入隐式状态 -
[](int a, double b) { return a == b; }—— 参数类型不一致,且无法适配泛型范围
正确示例:用首字母分段:[](const std::string& a, const std::string& b) { return a[0] == b[0]; };或按十位分组:[](int a, int b) { return a / 10 == b / 10; }。注意:若输入是 const 容器,谓词参数也得用 const T&。
输入范围必须是 forward_range,且不能是临时对象
std::views::chunk_by 内部需多次解引用同一位置迭代器(比如回溯比较前一元素),因此不支持仅满足 input_range 的单次读取流(如 std::istream_view)。更隐蔽的风险是生命周期:
-
auto v = std::vector{1,2,3,4,5} | std::views::chunk_by(pred);—— 编译可能通过,但v持有对临时std::vector的悬垂迭代器 - 正确做法:先绑定容器,再构造视图:
std::vector data = {1,2,3,4,5}; auto chunks = data | std::views::chunk_by(pred); -
std::forward_list可用,但std::list更稳妥(前者迭代器失效行为更敏感)
一旦原容器发生重分配(如 vector::push_back),所有已创建的 chunk_by_view::iterator 立即失效——这点比 std::views::chunk 严格得多。
它和 std::views::chunk 的根本区别在哪
二者名字相似,用途完全正交:
-
std::views::chunk(N):只认位置,无视值;输入必须是sized_range;输出块大小恒 ≤ N;末块自动截断 -
std::views::chunk_by(pred):只认相邻关系变化;输入只需forward_range;块大小完全由数据语义决定;不保证任何最小/最大长度
想实现“每 3 个一组”,别硬套 chunk_by。要么用 std::views::chunk(3),要么手写 iota + transform + subrange 组合(适用于 random_access_range)。拿错工具,越调越偏。


















