如何使用基于范围的循环语法遍历 STL 容器中的连续对?

2022-01-10 00:00:00 iterator c++ c++11

如何创建自定义类以使用基于范围的循环遍历 STL 容器中的连续项对?

How do I create a custom class to loop over consecutive pairs of items in a STL container using a range-based loop?

这是我想要的语法和输出:

This is the syntax and output I want:

std::list<int> number_list;
number_list.push_back(1);
number_list.push_back(2);
number_list.push_back(3);

auto paired_list = Paired(number_list);
for (const auto & pair : paired_list) {
  std::printf("The pair is (%d, %d)
", *(pair[0]), *(pair[1]));
  // or
  //std::printf("The pair is (%d, %d)
", *(pair.first), *(pair.second));
}
// output:
// The pair is (1, 2)
// The pair is (2, 3)

我知道需要这些(以及更多),但我想不通:

I know these (and more) are needed, but I can't figure it out:

template <class T>
class Paired {
  ???
  class iterator {
    ???
  }
  iterator begin() {
    ...
  }
  iterator end() {
    ...
  }
}

不用担心 const 修饰符.

没有提升.

不要修改或复制容器中的对象.

Do not modify or copy objects in the container.

推荐答案

这就是我要做的.

#include <iterator>
#include <utility>

template <typename FwdIt> class adjacent_iterator {
public:
    adjacent_iterator(FwdIt first, FwdIt last)
        : m_first(first), m_next(first == last ? first : std::next(first)) { }

    bool operator!=(const adjacent_iterator& other) const {
        return m_next != other.m_next; // NOT m_first!
    }

    adjacent_iterator& operator++() {
        ++m_first;
        ++m_next;
        return *this;
    }

    typedef typename std::iterator_traits<FwdIt>::reference Ref;
    typedef std::pair<Ref, Ref> Pair;

    Pair operator*() const {
        return Pair(*m_first, *m_next); // NOT std::make_pair()!
    }

private:
    FwdIt m_first;
    FwdIt m_next;
};

template <typename FwdIt> class adjacent_range {
public:
    adjacent_range(FwdIt first, FwdIt last)
        : m_first(first), m_last(last) { }

    adjacent_iterator<FwdIt> begin() const {
        return adjacent_iterator<FwdIt>(m_first, m_last);
    }

    adjacent_iterator<FwdIt> end() const {
        return adjacent_iterator<FwdIt>(m_last, m_last);
    }

private:
    FwdIt m_first;
    FwdIt m_last;
};

template <typename C> auto make_adjacent_range(C& c) -> adjacent_range<decltype(c.begin())> {
    return adjacent_range<decltype(c.begin())>(c.begin(), c.end());
}

#include <iostream>
#include <vector>
using namespace std;

void test(const vector<int>& v) {
    cout << "[ ";

    for (const auto& p : make_adjacent_range(v)) {
        cout << p.first << "/" << p.second << " ";
    }

    cout << "]" << endl;
}

int main() {
    test({});
    test({11});
    test({22, 33});
    test({44, 55, 66});
    test({10, 20, 30, 40});
}

打印出来:

[ ]
[ ]
[ 22/33 ]
[ 44/55 55/66 ]
[ 10/20 20/30 30/40 ]

注意事项:

  • 我没有对此进行详尽的测试,但它尊重前向迭代器(因为它不会尝试使用 ++、!= 和 * 之外的操作).

  • I haven't exhaustively tested this, but it respects forward iterators (because it doesn't try to use operations beyond ++, !=, and *).

range-for 要求极弱;它不需要转发迭代器应该提供的所有东西.因此,我已经达到 range-for 的要求,但仅此而已.

range-for has extremely weak requirements; it doesn't require all of the things that forward iterators are supposed to provide. Therefore I have achieved range-for's requirements but no more.

NOT m_first"注释与如何接近范围末尾有关.从空范围构造的相邻迭代器具有 m_first == m_next,它也是 == last.从 1 元素范围构造的相邻迭代器具有 m_first 指向该元素和 m_next == 最后.从多元素范围构造的相邻迭代器具有指向连续有效元素的 m_first 和 m_next.随着它的增加,最终 m_first 将指向最后一个元素,而 m_next 将是最后一个.next_range 的 end() 返回的内容是从 (m_last, m_last) 构造的.对于一个完全空的范围,这在物理上与 begin() 相同.对于 1+ 个元素范围,这在物理上与 begin() 不同,它在我们没有完整的对之前已经递增 - 这样的迭代器有 m_first 指向最后一个元素.但是如果我们根据迭代器的 m_next 比较迭代器,那么我们会得到正确的语义.

The "NOT m_first" comment is related to how the end of the range is approached. An adjacent_iterator constructed from an empty range has m_first == m_next which is also == last. An adjacent_iterator constructed from a 1-element range has m_first pointing to the element and m_next == last. An adjacent_iterator constructed from a multi-element range has m_first and m_next pointing to consecutive valid elements. As it is incremented, eventually m_first will point to the final element and m_next will be last. What adjacent_range's end() returns is constructed from (m_last, m_last). For a totally empty range, this is physically identical to begin(). For 1+ element ranges, this is not physically identical to a begin() that has been incremented until we don't have a complete pair - such iterators have m_first pointing to the final element. But if we compare iterators based on their m_next, then we get correct semantics.

NOT std::make_pair()"注释是因为 make_pair() 会衰减,而我们实际上需要一对引用.(我本可以使用 decltype,但 iterator_traits 也会告诉我们答案.)

The "NOT std::make_pair()" comment is because make_pair() decays, while we actually want a pair of references. (I could have used decltype, but iterator_traits will tell us the answer too.)

剩下的主要细节将围绕禁止将右值作为 make_adjacent_range 的输入(这样的临时人员不会延长他们的生命;委员会正在研究这个问题),并播放 ADL 舞蹈以尊重非成员开始/结束,以及内置数组.这些练习留给读者.

The major remaining subtleties would revolve around banning rvalues as inputs to make_adjacent_range (such temporaries would not have their lives prolonged; the Committee is studying this issue), and playing an ADL dance to respect non-member begin/end, as well as built-in arrays. These exercises are left to the reader.

相关文章