Obtaining item index in ranged based for on vector

前端 未结 2 980
别那么骄傲
别那么骄傲 2020-12-11 15:48

The C++11 introduced ranged-based for loop that is internally implemented using (const) iterators so this:

std::vector vec;

for(std::stri         


        
相关标签:
2条回答
  • 2020-12-11 16:06

    Yes, but I'd use vec.data() instead. A bonus of using .data() is that non-contiguous std containers don't have it, so your code reliably stops compiling when the container being iterated over doesn't work that way (like deque or std::vector<bool>). (There are other minor advantages, like std::addressof issues, and the fact it is well defined on empty containers, but those aren't as important especially here.)

    Alternatively we write an index_t iterator-like wrapper:

    template<class T>
    struct index_t {
      T t;
      T operator*()const{ return t; }
      void operator++() { ++t; }
      friend bool operator==( index_t const& lhs, index_t const& rhs ) {
        return lhs.t == rhs.t;
      }
      friend bool operator!=( index_t const& lhs, index_t const& rhs ) {
        return lhs.t != rhs.t;
      }
    };
    template<class T>
    index_t<T> index(T t) { return {t}; }
    

    index_t<int> can be used to create counting for(:) loops.

    index_t<iterator> can be used to create iterator-returning for(:) loops.

    template<class It>
    struct range_t {
      It b,e;
      It begin() const {return b;}
      It end() const {return e;}
    };
    template<class It>
    range_t<It> range( It s, It f ) { return {s,f}; }
    
    template<class T>
    range_t<index_t<T>>
    index_over( T s, T f ) {
      return {{{s}}, {{f}}};
    }
    template<class Container>
    auto iterators_of( Container& c ) {
      using std::begin; using std::end;
      return index_over( begin(c), end(c) );
    }
    

    we can now iterator over iterators of a container.

    for (auto it : iterators_of(vec))
    

    live example.


    The mentioned iterate-over-integers is:

    for (int i : index_over( 0, 100 ) )
    

    we can also directly get the indexes of the container:

    template<class Container>
    range_t< index_t<std::size_t> >
    indexes_of( Container& c ) {
      return index_over( std::size_t(0), c.size() );
    }
    template<class T, std::size_t N>
    range_t< index_t<std::size_t> >
    indexes_of( T(&)[N] ) {
      return index_over( std::size_t(0), N );
    }
    

    which lets us:

    for( auto i : indexes_of( vec ) )
    

    where i varies from 0 to vec.size()-1. I find this is easier to work with sometimes than a zip iterator or the like.


    Improvements omitted:

    Make index_t a real input_iterator. Use std::move and/or std::forward as needed in making indexes and ranges. Support Sentinals on ranges. Make range_t interface richer (size, optional random-access [], empty, front, back, range_t range_t::without_front(n) const, etc.

    0 讨论(0)
  • 2020-12-11 16:17

    Yes, that's a valid solution. The underlying data is guaranteed to be contiguous (std::vector is supposed to be a dynamic array, more or less).

    n4140 §23.3.6.1 [vector.overview]/1

    The elements of a vector are stored contiguously, meaning that if v is a vector<T, Allocator> where T is some type other than bool, then it obeys the identity &v[n] == &v[0] + n for all 0 <= n < v.size()

    0 讨论(0)
提交回复
热议问题