46

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.

hildensia
  • 1,650
  • 2
  • 12
  • 24
  • 1
    No, but it is not that hard to use boost.Range's `zip` and boost's `counting_iterator` together to that end. – Nobody moving away from SE Jul 22 '14 at 08:02
  • duplicated to something, and answer is count it yourself. – Bryan Chen Jul 22 '14 at 08:03
  • How about this answer: https://stackoverflow.com/a/12201788/760746 – Nobody moving away from SE Jul 22 '14 at 08: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. – EdChum Jul 22 '14 at 08:05
  • 1
    @EdChum: Associative container's iterators already return key and value. – Nobody moving away from SE Jul 22 '14 at 08:06
  • @Nobody we're talking about some alternative perhaps to iterators here, the point being that @graham.reeds answer would not work, also the pseudo code implies an index which would not work in the case of an associative container. It would make more sense to name it `it` – EdChum Jul 22 '14 at 08:08
  • Can you qualify what you mean by the statement `iterators don't give guarantees of the order they iterate over a container`? – EdChum Jul 22 '14 at 08:10
  • @EdChum: I don't see a problem here: Either you need the indexing, then you could use something like the `enumerate` in the answer I linked or you don't. If the loop was going over a `map` then you would get the `value_type` which is `pair` which is exactly what you wanted. – Nobody moving away from SE Jul 22 '14 at 08:11
  • AFAIK a iterator could traverse the container in any order it wants and not necessarily from front to end. If you then count yourself you might end up having the wrong index. – hildensia Jul 22 '14 at 08:11
  • @hildensia: Depending on the container there are some guarantees on the order of traversal. Only the `unordered` containers might give you some problem but they also guarantee that you get each element only once so what is the problem? – Nobody moving away from SE Jul 22 '14 at 08:13
  • Okay, I didn't know. So 'count yourself' is a solution. Still I don't find it very convenient... – hildensia Jul 22 '14 at 08:15
  • I would think that accessing using an iterator is a consistent enough way to iterate over a container, plus you have lots of algorithms that will operate over the entire container or defined ranges, the index counting itself you'd have to add, personally I don't find I need this that often. – EdChum Jul 22 '14 at 08:19
  • It's unclear what you want `idx` for; if it is an index to later access the element, then this only really works for sequential containers. Is it okay ? Are you looking for something more generic ? (but then, what do you substitute the index for ?) – Matthieu M. Jul 22 '14 at 08:26
  • I often use it to iterate over two containers simultaneously. So `idx` would really be an index of a sequential container. – hildensia Jul 22 '14 at 08:44
  • @EdChum: No where in the OP does it mention what sort of container he is using and he wanted the index so my suggestion of just using a normal for loop is valid - the last time I looked for(;;) was still valid C++! – graham.reeds Jul 22 '14 at 08:48

5 Answers5

25

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;
    }
}
Adrian Maire
  • 14,354
  • 9
  • 45
  • 85
CK1
  • 694
  • 6
  • 13
  • 6
    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 Feb 06 '20 at 03:09
  • @zeromus, _fair use_ is a thing [even with the GPL](https://www.gnu.org/licenses/gpl-faq.en.html#GPLFairUse), so your "WARNING" is moot: **no violation here**. (Actually, even the term "distribution" itself is [ill-defined](https://opensource.stackexchange.com/a/10487/7445) 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. Dec 17 '22 at 14:20
  • @Sz. The post timeline indicates that the content was provided under CC-BY-SA-3.0 https://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 – zeromus Dec 18 '22 at 21:43
24

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.

Adrian Maire
  • 14,354
  • 9
  • 45
  • 85
12

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)

Zitrax
  • 19,036
  • 20
  • 88
  • 110
  • 1
    [Official documentation](https://www.boost.org/doc/libs/1_72_0/libs/range/doc/html/range/reference/adaptors/reference/indexed.html) may also be useful. – Franklin Yu Feb 19 '20 at 00:42
8

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.

underscore_d
  • 6,309
  • 3
  • 38
  • 64
3

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

for (int idx=0; idx<num; ++idx)
{
// do stuff
}
graham.reeds
  • 16,230
  • 17
  • 74
  • 137