通过 C++ ranged-for 循环机制支持跨步迭代

问题描述

C++ ranged-for 循环的概念“模型”是推进“当前”迭代器,直到到达迭代结束的某个“点”。更具体地说,写作:

https://......azuredatabricks.net/basic-auth/api/2.0/token/list

is 等价于写作(稍微简化并忽略生命周期延长需求等):

for(auto x : range) /* ... */

但是当终点不是一个点,而是一组更大的可能状态/值时会发生什么?

具体来说,假设我们正在迭代一系列整数,但在每次迭代时推进 k,而不仅仅是 1。在经典的、简单的、C 风格的循环中,这将按如下方式工作:

auto __end = range.end();
for (auto __iter = range.begin(); __iter != __end; ++__iter) {
    auto x = *__iter;
    /* ... */
} 

并注意比较使用的是顺序关系,而不是相等。如果一个人天真地实现了一个跨步迭代器,用

for (i = n_start; i < n_end; i += k) /* ... */

然后是一个整数范围类,它的 strided_iterator& strided_iterator::operator++() { pos_ += stride_; return *this; } bool operator==(const strided_iterator& lhs,const strided_iterator& rhs) { return lhs.pos_ == rhs.pos_; } begin() 返回 end() - 这个范围上的一个 ranged-for 循环通常是一个无限循环 :范围为 strided_iterator 会直接跳过 __iter - 从低于 __end 到更高,永不停止。

我的问题:

  1. 在 C++17 中,我可以通过定义哨兵类型和“作弊”来克服这个问题:让它对 __end 的“相等”运算符实际上执行顺序比较。但是有没有更优雅的解决方案?

  2. 在 C++14 或更早版本中,我可以实现一个更丑陋的 hack:一个具有两个“模式”的迭代器类,一个常规跨步迭代器和一个如上所述的哨兵,构造函数选择哪种模式迭代器使用。或者 - 一个虚拟运算符==和这两种模式的两个子类。有没有更优雅的解决方案?

  3. 为什么 ranged-for 循环不允许基于顺序而不是基于相等的比较,不知何故?或者更一般地说——用迭代结束的谓词替换哨兵的概念,在每次迭代后应用于迭代器?


PS - 如果您认为我应该将 (3.) 拆分成一个单独的问题,请在评论中这么说。

解决方法

对 (1.) 的回答:

您可以采用的一种方法是 Eric Niebler 在他的 range-v3 库中:

  • 使用有界跨步迭代器而不是无界迭代器。
  • 构造跨步迭代器,边界为范围的结尾。

现在,迭代器的前进成员将包含一个边界检查,因此它不会前进到末尾,而是设置到末尾(或者我应该说是末尾的位置)。然后,它实际上将比较等于定义为封装给定整数位置的简单无步长迭代器的结束哨兵。

从表面上看,这意味着在每次迭代中,用于检查是否退出循环的指令数量将增加一倍:半编译伪代码类似于

/* ... */
pos <- pos + stride
if (pos > end_pos)
   pos <- end_pos
if (pos == end_pos)
   break the loop
next iteration

一个亲切的编译器可能会注意到只检查 pos >= end_pos 就足够了。

这将适用于 C++17。它不会帮助您解决哨兵必须与 C++14 及更早版本中的迭代器具有相同类型的问题。


感谢@Bob 建议我查看 range-v3 的步幅适配器。需要深入了解我提出的问题在哪里解决...