Get index in C++11 foreach loop

40,451

Solution 1

A good implementation of the feature you are requested can be found here:

https://github.com/ignatz/pythonic

The idea behind is, that you build a wrapper struct with a custom iterator that does the counting. Below is a very minimal exemplary implementation to illustrate the idea:

// Distributed under the terms of the GPLv2 or newer

#include <iostream>
#include <vector>
#include <tuple>

// Wrapper class
template <typename T>
class enumerate_impl
{
public:
    // The return value of the operator* of the iterator, this
    // is what you will get inside of the for loop
    struct item
    {
        size_t index;
        typename T::value_type & item;
    };
    typedef item value_type;

    // Custom iterator with minimal interface
    struct iterator
    {
        iterator(typename T::iterator _it, size_t counter=0) :
            it(_it), counter(counter)
        {}

        iterator operator++()
        {
            return iterator(++it, ++counter);
        }

        bool operator!=(iterator other)
        {
            return it != other.it;
        }

        typename T::iterator::value_type item()
        {
            return *it;
        }

        value_type operator*()
        {
            return value_type{counter, *it};
        }

        size_t index()
        {
            return counter;
        }

    private:
        typename T::iterator it;
        size_t counter;
    };

    enumerate_impl(T & t) : container(t) {}

    iterator begin()
    {
        return iterator(container.begin());
    }

    iterator end()
    {
        return iterator(container.end());
    }

private:
    T & container;
};

// A templated free function allows you to create the wrapper class
// conveniently 
template <typename T>
enumerate_impl<T> enumerate(T & t)
{
    return enumerate_impl<T>(t);
}



int main()
{
    std::vector<int> data = {523, 1, 3};
    for (auto x : enumerate(data))
    {
        std::cout << x.index << ": " << x.item << std::endl;
    }
}

Solution 2

What about a simple solution like:

int counter=0;
for (auto &val: container)
{
    makeStuff(val, counter);

    counter++;
}

You could make a bit more "difficult" to add code after the counter by adding a scope:

int counter=0;
for (auto &val: container)
{{
    makeStuff(val, counter); 
}counter++;}

As @graham.reeds pointed, normal for loop is also a solution, that could be as fast:

int counter=0;
for (auto it=container.begin(); it!=container.end(); ++it, ++counter)
{
    makeStuff(val, counter);
}

And finally, a alternative way using algorithm:

int counter = 0;
std::for_each(container.begin(), container.end(), [&counter](int &val){ 
    makeStuff(val, counter++);
});

Note: the order between range loop and normal loop is guaranteed by the standard 6.5.4. Meaning the counter is able to be coherent with the position in the container.

Solution 3

If you have access to Boost it's range adaptors can be used like this:

#include <boost/range/adaptor/indexed.hpp>
using namespace boost::adaptors;
 
for (auto const& elem : container | indexed(0))
{
    std::cout << elem.index() << " - " << elem.value() << '\n';
}

Source (where there are also other examples)

Solution 4

C++17 and structured bindings makes this look OK - certainly better than some ugly mutable lambda with a local [i = 0](Element&) mutable or whatever I've done before admitting that probably not everything should be shoehorned into for_each() et al. - and than other solutions that require a counter with scope outside the for loop.

for (auto [it, end, i] = std::tuple{container.cbegin(), container.cend(), 0};
     it != end; ++it, ++i)
{
      // something that needs both `it` and `i`ndex
}

You could make this generic, if you use this pattern often enough:

template <typename Container>
auto
its_and_idx(Container&& container)
{
    using std::begin, std::end;
    return std::tuple{begin(container), end(container), 0};
}

// ...

for (auto [it, end, i] = its_and_idx(foo); it != end; ++it, ++i)
{
    // something
}

C++ Standard proposal P2164 proposes to add views::enumerate, which would provide a view of a range giving both reference-to-element and index-of-element to a user iterating it.

We propose a view enumerate whose value type is a struct with 2 members index and value representing respectively the position and value of the elements in the adapted range.

[ . . .]

This feature exists in some form in Python, Rust, Go (backed into the language), and in many C++ libraries: ranges-v3, folly, boost::ranges (indexed).

The existence of this feature or lack thereof is the subject of recurring stackoverflow questions.

Hey, look! We're famous.

Solution 5

If you need the index then a traditional for works perfectly well.

for (int idx=0; idx<num; ++idx)
{
// do stuff
}
Share:
40,451
hildensia
Author by

hildensia

Updated on July 09, 2022

Comments

  • hildensia
    hildensia almost 2 years

    Is there a convenient way to get the index of the current container entry in a C++11 foreach loop, like enumerate in python:

    for idx, obj in enumerate(container):
        pass
    

    I could imagine an iterator that can also return the index or similar.

    Of course I could have a counter, but often iterators don't give guarantees of the order they iterate over a container.

  • Bryan Chen
    Bryan Chen almost 10 years
    this can be slow. e.g. linked list
  • andrewmu
    andrewmu almost 10 years
    Maybe, but no mention of performance was given and no mention of linked list. Why would an iterator be any faster on a linked list?
  • hildensia
    hildensia almost 10 years
    Because it can internally save the current node and traverse from there. No random access is needed.
  • Bryan Chen
    Bryan Chen almost 10 years
    because linked list doesn't support random access. and it won't work for unordered containers e.g. set and map
  • sigalor
    sigalor over 7 years
    When you use iterators, you can use std::distance(container.begin(), it) instead of a counter variable.
  • Adrian Maire
    Adrian Maire over 7 years
    @sigalor: depending on the type of iterator, std::distance may be O(n), which is not as good as the counter (O(k)).
  • Felix Dombek
    Felix Dombek about 6 years
    @AdrianMaire your last example doesn't seem to increment the counter.
  • Adrian Maire
    Adrian Maire about 6 years
    Thanks, should be fixed now.
  • underscore_d
    underscore_d over 4 years
    @sigalor Yes, but that's arguably wasteful, especially if there are a lot of elements and/or the container is one for which iterator arithmetic is expensive.
  • zeromus
    zeromus over 4 years
    WARNING: believe it or not, if you follow the link to the source, this is GPL code. ... ... I know, right? I believe CK1 is (or was, several years ago) a work collaborator of github ignatz. This answer was posted after the github code. Did CK1 find it licensed differently at the office? Was it posted here in violation of the license? Clarification would be welcome.
  • Franklin Yu
    Franklin Yu about 4 years
    Official documentation may also be useful.