3

I have defined a deque of pointers like this:

std::deque<BoardSquare *> mydeque;

I would like to use range based for loops with my deque:

for (BoardSquare * b : mydeque) {

    // do something involving the index of b
}

Is it possible to get the index of an item from within a range based for loop?

14
  • 14
    If you need the index, maybe you shouldn't be using the range based for loop. Commented Jul 19, 2013 at 15:29
  • 1
    @wlyles: That gets an item from an index, not an index from an item. Commented Jul 19, 2013 at 15:32
  • 4
    @BeeBand: The "awfulness" is that you'll need an extra variable to keep track of the index, and it would be simpler just to use that (or an iterator) as the loop variable in an old-fashioned for loop. Commented Jul 19, 2013 at 15:35
  • 3
    @BeeBand Or just maybe the comment upvotes are working the intended way, and people are upvoting because it is a good comment. You're getting annoyed because the answer to your question turned out to be don't do it that way to begin with? If you're so hell bent on using a range based for loop, go ahead and do what Nawaz posted. Commented Jul 19, 2013 at 15:42
  • 1
    @BeeBand That's why it was just a comment and not an answer. Commented Jul 19, 2013 at 15:52

4 Answers 4

9

No, it's not (at least not in a reasonable way). When you really need the index, then you should probably not use the range-based for loop at all, but rather a good old iterator- or index-based for-loop:

// non-idiomatic index-iteration, random access containers only
for(std::size_t i=0; i<mydeque.size(); ++i)
    mydeque[i];

// awfully ugly additional iteration variable, yet generic and fast
std::size_t i = 0;
for(auto iter=mydeque.begin(); iter!=mydeque.end(); ++iter,++i)
    *iter;

// idiomatic and generic, yet slow for non-random access containers
for(auto iter=mydeque.begin(); iter!=mydeque.end(); ++iter)
{
    auto i = std::distance(mydeque.begin(), iter);
    *iter;
}

Still all those have their advantages and disadvantages regarding clarity, idiomaticity, streamlinedness, and perfomance.

Sign up to request clarification or add additional context in comments.

1 Comment

see my answer for a dirty way to get an index in a range-for loop.
8

I came up with a solution — or rather an experimental solution. Here is how you will end up using it:

for(auto item : make_indexable(v))
{
      //std::get<0>(item) is the index
      //std::get<1>(item) is the object
}

And here goes the minimal implementation (it is just to demonstrate the basic idea):

#include <tuple>
#include <functional>

template<typename C>
struct indexed_container
{
    struct indexed_iterator
    {
        typedef typename C::value_type value_type;
        typedef std::tuple<size_t, std::reference_wrapper<value_type>> tuple_type;

        typename C::iterator _it;
        size_t _index;

        indexed_iterator(typename C::iterator it) : _it(it), _index(0) {}

        indexed_iterator& operator++()
        {
            ++_it;
            ++_index;
            return *this;
        }
        bool operator == (indexed_iterator const & other)
        {
            return _it == other._it;
        }
        bool operator != (indexed_iterator const & other)
        {
            return _it != other._it;
        }
        tuple_type operator*()
        {
            return std::make_tuple(_index, std::ref(*_it));
        }
    };

    indexed_container(C & c) : _c(c) {}

    indexed_iterator begin()
    {
        return indexed_iterator(_c.begin());
    }
    indexed_iterator end()
    {
        return indexed_iterator(_c.end());
    }
private:
    C & _c;
};

template<typename C>
auto make_indexable(C & c) -> indexed_container<C>
{
    return indexed_container<C>(c); 
}

Test code:

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v{1,2,3};
    for(auto item : make_indexable(v))
    {
        std::cout << std::get<0>(item) << " => " << std::get<1>(item) << std::endl;

        std::get<1>(item) *= 10; //modify value!
    }
    std::cout << "\nModified\n";
    for(auto item : make_indexable(v))
    {
        std::cout << std::get<0>(item) << " => " << std::get<1>(item) << std::endl;
    }
}

Output:

0 => 1
1 => 2
2 => 3

Modified
0 => 10
1 => 20
2 => 30

Online demo

Note that this solution is not perfect, as it will not work with temporaries and const containers (and containers of const objects). Also, right now the underlying object will be returned as reference even if you write auto item as opposed to auto & item (in fact, you cannot write auto &item). But I think these issues can be fixed with a bit more effort and careful design. After all, it is just a demonstration of the basic idea.

Comments

3

You'd need to add an extra variable to keep track of the index, duplicating the (inaccessible) iterator used by the range-based loop. You'd need to make sure it's correctly initialised and incremented on each iteration, being especially careful that it won't go wrong if someone adds continue statements to the loop.

It would be simpler and less error-prone to use the index (or an iterator, from which you can calculate the index when needed) as the iteration variable of a conventional for loop.

1 Comment

Have a look at my solution. I would like to hear your comments on that.
2

In addition to the excellent answer by @ChristianRau which shows the preferred approaches to obtain loop indices, there is a way to get the index from a ranged-for loop, but only if you use a std::vector, since that is the only container that guarantees element contiguity in memory.

#include <deque>
#include <vector>
#include <iostream>

int main() 
{
    auto v = std::vector<int> { 0, 1, 2, 3 };
    auto d = std::vector<int*> { &v[0], &v[1], &v[2], &v[3] }; // NOT: std::deque

    for (auto ptr: d)
    {
        // assumes element contiguity, only guaranteed for std::vector!!
        auto const i = std::distance(&d[0], &ptr);
        std::cout << *(d[i]) << "\n";
    }
}

Live output, will crash for std::deque.

1 Comment

Well, when taking the elements by reference instead of by value it's even easier (yet no less dirty): for(auto&& b : v) auto i = &b - v.data();, without the need for an additional vector. Ok, you have to remember to take references, but in your version you would need to iterate a completely different container anyway (which justifies the question if this is still the same range based for loop). Still interesting approach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.