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

后端 未结 4 522
深忆病人
深忆病人 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:05

    Okay, an hour with no answers, I've come up with a solution which works. Note that this uses my own FixedLengthVector which is exactly what it sounds like.

    template <class T>
    class Grouped {
    private:
      // length of grouped objects
      static const unsigned length_ = 2;
      // hold pointer to base container to avoid comparing incompatible iterators
      T * base_container_;
    public:
      // constructor
      Grouped(T & base_container) :
          base_container_(&base_container) {
      }
      // iterator
      class iterator {
      private:
        // hold pointer to base container to avoid comparing incompatible iterators
        T * base_container_;
        // hold pointers to objects in base container
        FixedLengthVector<length_, typename T::value_type *> ptr_;
        // hold iterator to last object
        typename T::iterator last_iterator_;
      public:
        // constructor
        iterator(T & base_container, typename T::iterator & it)
            : base_container_(&base_container),
              last_iterator_(it) {
          // set up pointers if possible
          unsigned i = 0;
          // check for end iterator
          if (last_iterator_ == base_container_->end()) {
            ptr_.fill(NULL);
            return;
          }
          // set up first object
          ptr_[0] = &*last_iterator_;
          // set up next objects
          for (unsigned i = 1; i < length_; ++i) {
            ++last_iterator_;
            if (last_iterator_ == base_container_->end()) {
              ptr_.fill(NULL);
              return;
            }
            ptr_[i] = &*last_iterator_;
          }
        }
        // dereference operator
        FixedLengthVector<length_, typename T::value_type *> & operator * (void) {
          assert(ptr_[0] != NULL);
          return ptr_;
        }
        // pre-increment
        iterator & operator ++ (void) {
          // can't increase past end
          assert(last_iterator_ != base_container_->end());
          // find next iterator
          ++last_iterator_;
          if (last_iterator_ == base_container_->end()) {
            ptr_.fill(NULL);
            return * this;
          }
          // cycle pointers left
          for (unsigned i = 1; i < length_; ++i) {
            ptr_[i - 1] = ptr_[i];
          }
          ptr_[length_ - 1] = &*last_iterator_;
          return * this;
        }
        // equality comparison
        bool operator == (const iterator & that) const {
          return base_container_ == that.base_container_ &&
                 last_iterator_ == that.last_iterator_;
        }
        // inequality comparison
        bool operator != (const iterator & that) const {
          return !(*this == that);
        }
      };
      // end iterator
      iterator end() {
        return iterator(*base_container_, base_container_->end());
      }
      // begin iterator
      iterator begin() {
        return iterator(*base_container_, base_container_->begin());
      }
    };
    
    0 讨论(0)
  • 2020-12-10 10:09

    edit I was using transform.

    Use adjacent_difference.

    The second version takes a binary function which transforms the two values into a new (different) value:

    string make_message(int first, int second) {
        ostringstream oss;
        oss << "The pair is (" << first << ", " << second << ")";
        return oss.str();
    }
    

    We can now transform the adjacent pairs into a third range. We'll use the ostream_iterator to use cout like a range:

    list<int> numbers;
    //...
    adjacent_difference(numbers.begin(), numbers.end(),
                        ostream_iterator<string>(cout, "\n"),
                        make_message);
    

    2nd edit

    I found a question on comp.lang.c++.moderated asking why there aren't more 'adjacent' functions in the standard library such as for_each_adjacent. The reply said they were trivial to implement using std::mismatch.

    I think this would be a better direction to go than implementing a special adjacent iterator.

    0 讨论(0)
  • 2020-12-10 10:13

    Here's what I would do.

    #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});
    }
    

    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.

    0 讨论(0)
  • 2020-12-10 10:13

    Try this.

    #include <list>
    #include <iostream>
    
    template<class T, class TIter = typename T::iterator, class TVal = typename T::value_type>
    class PairedImpl {
        T& m_t;
    public:
        class Iter {
            TIter m_it;
        public:
            Iter(const TIter & it) : m_it(it)  {}
    
            bool  operator!=(const Iter& it)   {         return m_it != it.m_it; }
            Iter& operator++()                 { ++m_it; return *this; }
            const Iter & operator *() const    {         return *this; }
            const TVal & first()      const    {         return *m_it; }
            const TVal & second()     const    {         return *std::next(m_it); }
        };
    
        PairedImpl(T& t) : m_t(t) {}
    
        Iter begin() { return Iter(m_t.begin()); }
    
        Iter end() {
            TIter end = m_t.end();
            return Iter(m_t.empty() ? end : --end);
        }
    };
    
    template<class T>
    PairedImpl<T> Paired(T& t) {
        return PairedImpl<T>(t);
    }
    

    Usage

    int main()
    {
        std::list<int> lst;
        lst.push_back(1);
        lst.push_back(2);
        lst.push_back(3);
        lst.push_back(4);
        lst.push_back(5);
    
        for (const auto & pair : Paired(lst)) {
            std::cout << "(" << pair.first() << ", " << pair.second() << ")" << std::endl;
        }
        return 0;
    }
    
    0 讨论(0)
提交回复
热议问题