How do I loop over consecutive pairs in an STL container using range-based loop syntax?

后端 未结 4 531
深忆病人
深忆病人 2020-12-10 09:21

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:

<         


        
4条回答
  •  夕颜
    夕颜 (楼主)
    2020-12-10 10:13

    Here's what I would do.

    #include 
    #include 
    
    template  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::reference Ref;
        typedef std::pair Pair;
    
        Pair operator*() const {
            return Pair(*m_first, *m_next); // NOT std::make_pair()!
        }
    
    private:
        FwdIt m_first;
        FwdIt m_next;
    };
    
    template  class adjacent_range {
    public:
        adjacent_range(FwdIt first, FwdIt last)
            : m_first(first), m_last(last) { }
    
        adjacent_iterator begin() const {
            return adjacent_iterator(m_first, m_last);
        }
    
        adjacent_iterator end() const {
            return adjacent_iterator(m_last, m_last);
        }
    
    private:
        FwdIt m_first;
        FwdIt m_last;
    };
    
    template  auto make_adjacent_range(C& c) -> adjacent_range {
        return adjacent_range(c.begin(), c.end());
    }
    
    #include 
    #include 
    using namespace std;
    
    void test(const vector& 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});
    }
    

    This prints:

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

    Notes:

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

    • 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.

    • 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.

    • 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.)

    • 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.

提交回复
热议问题