54

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.

15
  • 1
    No, but it is not that hard to use boost.Range's zip and boost's counting_iterator together to that end. Commented Jul 22, 2014 at 8:02
  • duplicated to something, and answer is count it yourself. Commented Jul 22, 2014 at 8:03
  • How about this answer: stackoverflow.com/a/12201788/760746 Commented Jul 22, 2014 at 8:04
  • That sounds nice but it would only work for sequential containers, not for associative containers. For associative containers you would need the key, not the index. Commented Jul 22, 2014 at 8:05
  • 1
    @EdChum: Associative container's iterators already return key and value. Commented Jul 22, 2014 at 8:06

5 Answers 5

28

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.

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

5 Comments

When you use iterators, you can use std::distance(container.begin(), it) instead of a counter variable.
@sigalor: depending on the type of iterator, std::distance may be O(n), which is not as good as the counter (O(k)).
@AdrianMaire your last example doesn't seem to increment the counter.
Thanks, should be fixed now.
@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.
26

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

3 Comments

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.
@zeromus, fair use is a thing even with the GPL, so your "WARNING" is moot: no violation here. (Actually, even the term "distribution" itself is ill-defined in GPLv2, so you'd have a hard time at court proving that this is such a case, esp. as you yourself made it crystal clear that the actual copyright holder (of the cited software) can be identified with no problem, exactly because CK1 made it explicitly traceable (albeit arguably the attribution could've been better).)
@Sz. The post timeline indicates that the content was provided under CC-BY-SA-3.0 stackoverflow.com/posts/24881903/timeline ; it is a violation of the GPL to remove the GPL license and attach a different one. This is what was done in 2014; another user NOT CK1 wrote that it was licensed under GPL, presumably in response to my response from 2020. One could now argue that the entire answer is legitimately CC-BY-SA along with a clearly demarcated GPL segment. That would make it fair use by appending commentary, although I think it's still toxic for SO which wants CC-BY-SA all over
13

If you have access to Boost its 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)

1 Comment

Official documentation may also be useful.
10

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.

1 Comment

P2164's views::enumerate was indeed added to C++23.
3

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

for (int idx=0; idx<num; ++idx)
{
// do stuff
}

4 Comments

this can be slow. e.g. linked list
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?
Because it can internally save the current node and traverse from there. No random access is needed.
because linked list doesn't support random access. and it won't work for unordered containers e.g. set and map

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.