57

Possible Duplicate:
Find position of element in C++11 range-based for loop?

I have a vector and I would like to iterate it and, at the same time, have access to the indexes for each individual element (I need to pass both the element and its index to a function). I have considered the following two solutions:

std::vector<int> v = { 10, 20, 30 };

// Solution 1
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx)
    foo(v[idx], idx);

// Solution 2
for (auto it = v.begin(); it != v.end(); ++it)
    foo(*it, it - v.begin());

I was wondering whether there might be a more compact solution. Something similar to Python's enumerate. This is the closest that I got using a C++11 range-loop, but having to define the index outside of the loop in a private scope definitely seems to be like a worse solution than either 1 or 2:

{
    int idx = 0;
    for (auto& elem : v)
        foo(elem, idx++);
}

Is there any way (perhaps using Boost) to simplify the latest example in such a way that the index gets self-contained into the loop?

Community
  • 1
  • 1
betabandido
  • 18,946
  • 11
  • 62
  • 76
  • 7
    Why simplify simple things? :-) – Kos Jul 04 '12 at 11:34
  • 1
    You would have to create a generator-like function/object that returns std::pair and use the first and second fields of the pair. You could probably use macros to do the trick, but there is no handy and elegant way to use the Python-like syntax in C++. Your second solution is probably the best thing to do. – Morwenn Jul 04 '12 at 11:41
  • @Kos I am pretty much okay with solution 2. Just curious if there is even a simpler way :) – betabandido Jul 04 '12 at 11:43
  • 2
    Here it is: http://reedbeta.com/blog/python-like-enumerate-in-cpp17/ – Severin Pappadeux Mar 25 '19 at 20:05
  • 1
    What about: ` for ( auto x : v | boost::adaptors::indexed(0) ) { std::cout << x.value() << "," << x.index() << std::endl; }`? Needs the corresponding boost header of course. – user52366 May 23 '19 at 07:05
  • 1
    [std::views::enumerate](https://en.cppreference.com/w/cpp/ranges/enumerate_view) is a C++23 solution. – Steve Ward Aug 09 '23 at 01:42

3 Answers3

32

Here is some kind of funny solution using lazy evaluation. First, construct the generator object enumerate_object:

template<typename Iterable>
class enumerate_object
{
    private:
        Iterable _iter;
        std::size_t _size;
        decltype(std::begin(_iter)) _begin;
        const decltype(std::end(_iter)) _end;

    public:
        enumerate_object(Iterable iter):
            _iter(iter),
            _size(0),
            _begin(std::begin(iter)),
            _end(std::end(iter))
        {}

        const enumerate_object& begin() const { return *this; }
        const enumerate_object& end()   const { return *this; }

        bool operator!=(const enumerate_object&) const
        {
            return _begin != _end;
        }

        void operator++()
        {
            ++_begin;
            ++_size;
        }

        auto operator*() const
            -> std::pair<std::size_t, decltype(*_begin)>
        {
            return { _size, *_begin };
        }
};

Then, create a wrapper function enumerate that will deduce the template arguments and return the generator:

template<typename Iterable>
auto enumerate(Iterable&& iter)
    -> enumerate_object<Iterable>
{
    return { std::forward<Iterable>(iter) };
}

You can now use your function that way:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& a: enumerate(vec)) {
        size_t index = std::get<0>(a);
        double& value = std::get<1>(a);

        value += index;
    }
}

The implementation above is a mere toy: it should work with both const and non-const lvalue-references as well as rvalue-references, but has a real cost for the latter though, considering that it copies the whole iterable object several times. This problem could surely be solved with additional tweaks.

Since C++17, decomposition declarations even allow you to have the cool Python-like syntax to name the index and the value directly in the for initializer:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& [index, value] : enumerate(vec)) {
        value += index;
    }
}

A C++-compliant compiler decomposes auto&& inferring index as std::size_t&& and value as double&.

John Cvelth
  • 522
  • 1
  • 6
  • 19
Morwenn
  • 21,684
  • 12
  • 93
  • 152
  • 1
    Your code will blow up horribly if you pass a temporary to `enumerate`. – Xeo Jul 04 '12 at 13:04
  • Which compiler are you using? It does not compile with g++ 4.6 or 4.7. – betabandido Jul 04 '12 at 13:17
  • @Xeo Isn't it funny? You're probably right and I don't really see how to make a safer version. Anyway, using that function is not even as handy as the plain old solution. – Morwenn Jul 04 '12 at 13:19
  • @betabandido I'm using MinGW g++ 4.7.0. – Morwenn Jul 04 '12 at 13:19
  • 3
    You need to make the type of `_iter` change depending on whether the argument to `enumerate` is an rvalue. This is easier than it seems, I've edited the solution in and also fixed your `begin` and `end` functions. This works because `Iterable` in `enumerate` will be deduced as `T&` if you pass an lvalue (making the `_iter` member a simple reference), and `T` if you pass an rvalue (moving the argument into `_iter` in the ctor). – Xeo Jul 04 '12 at 13:54
  • The `operator !=` implementation looks extremely weird to me. Can anybody explain? – ulidtko Aug 30 '17 at 13:01
  • Wouldnt this make a copy of the Iterable every time? – Mac Oct 23 '17 at 08:13
  • @Mac I wasn't sure anymore, so I just checked with an online compiler: when given a possible const reference to an iterable, it stores a reference to said iterable, and not the iterable itself, so it should only make a copy when given a non-lvalue-reference iterable. – Morwenn Oct 23 '17 at 08:20
  • 2
    I've just found [cppitertools](https://github.com/ryanhaining/cppitertools) which goes in a similar direction, but you would use `a.index` and `a.element`. See Enumerate examples and enumerate.hpp – hsandt Mar 14 '18 at 16:03
  • @Morwenn For anyone wondering this is the one case (afaik) that `Iterable` would be deduced as an actual reference type, `T&` instead of just a `T` (if passed an lvalue). – Mike Lui Dec 22 '18 at 23:36
  • @hsandt I see that cppitertools are using structured bindings; I guess they changed their API from `a.index`? – Franklin Yu Mar 07 '21 at 01:30
  • Actually they kept the members, but added support for structured bindings: "They are basic structs with a .index and a .element, and also work with structured binding declarations." The README and unit test commit date from October 15, 2020, although I didn't check where/when the actual source file was changed, but it was probably that day or a little before. – hsandt Mar 07 '21 at 20:03
27

As @Kos says, this is such a simple thing that I don't really see the need to simplify it further and would personally just stick to the traditional for loop with indices, except that I'd ditch std::vector<T>::size_type and simply use std::size_t:

for(std::size_t i = 0; i < v.size(); ++i)
    foo(v[i], i);

I'm not too keen on solution 2. It requires (kinda hidden) random access iterators which wouldn't allow you to easily swap the container, which is one of the strong points of iterators. If you want to use iterators and make it generic (and possibly incur a performance hit when the iterators are not random access), I'd recommend using std::distance:

for(auto it(v.begin()); it != v.end(); ++it)
    foo(*it, std::distance(it, v.begin());
Antoine
  • 3,880
  • 2
  • 26
  • 44
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 4
    Given that any attempt to get anywhere close to Python's enumerate seems to end up in a huge code bloat, I think it is better to just use any of these two solutions. – betabandido Jul 04 '12 at 13:19
  • 2
    These days, writing a custom loop is no small matter. These days, standard library algorithms + range-for loops should take care of most of what you need, and if they don't - either you're doing something dangerous; or you have been lazy in writing your containers; or you're writing C++98-style code. – einpoklum Jan 26 '19 at 21:51
  • @einpoklum I still fight with such loops when there is a secondary property "index from 0" or comparable, which you *need* in that loop. This happens frequently when you have data in two or more iterators which is related via a common index and sometimes (and sometimes not) are working together in conjunction: e.g. an array of bit masks and a counter for each bit, where you need the mask to test presence of a "1" and increment the counter. I'd be grateful for an elegant idiom for my beginner C++ skillset. `if (pattern&msk[i]) cnt[i]++;` – Vroomfondel Jan 11 '22 at 12:14
1

One way is to wrap the loop in a function of your own.

#include <iostream>
#include <vector>
#include <string>

template<typename T, typename F>
void mapWithIndex(std::vector<T> vec, F fun) {
   for(int i = 0; i < vec.size(); i++) 
       fun(vec[i], i); 
}

int main() {
   std::vector<std::string> vec = {"hello", "cup", "of", "tea"};
   mapWithIndex(vec, [](std::string s, int i){
      std::cout << i << " " << s << '\n';
   } );
}
SingerOfTheFall
  • 29,228
  • 8
  • 68
  • 105
Karolis Juodelė
  • 3,708
  • 1
  • 19
  • 32
  • 2
    IMO this only complicates things further... – SingerOfTheFall Jul 04 '12 at 11:53
  • 3
    You make a fair point. Normally, a plain for loop is best. Apparently the OP doesn't want one though. – Karolis Juodelė Jul 04 '12 at 12:00
  • 3
    I actually wanted to further simplify the code (if possible, of course). `for idx, elem in enumerate(v): foo(idx, elem)` seems to me like simpler than any other solution posted in my question or in the answers. But, of course, that is a Python solution, and I was asking for a C++ one. – betabandido Jul 04 '12 at 12:09