Get index in C++11 foreach loop
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 astruct
with 2 membersindex
andvalue
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
}
hildensia
Updated on July 09, 2022Comments
-
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 almost 10 yearsthis can be slow. e.g. linked list
-
andrewmu almost 10 yearsMaybe, 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 almost 10 yearsBecause it can internally save the current node and traverse from there. No random access is needed.
-
Bryan Chen almost 10 yearsbecause linked list doesn't support random access. and it won't work for unordered containers e.g.
set
andmap
-
sigalor over 7 yearsWhen you use iterators, you can use
std::distance(container.begin(), it)
instead of a counter variable. -
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 about 6 years@AdrianMaire your last example doesn't seem to increment the counter.
-
Adrian Maire about 6 yearsThanks, should be fixed now.
-
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 over 4 yearsWARNING: 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 about 4 yearsOfficial documentation may also be useful.