31

Suppose you want to know the numerical index of an element when iterating a container that doesn't offer random access iterators. For example:

std::list<std::string> items;
int i = 0;
for (auto & item : items) item += std::to_string(i++);

Is there a more idiomatic or nicer way of doing this? I presume this pattern comes up in various situations. I don't like the integer index being available outside of the loop. Bracketing the loop and the index definition in a local block seems ugly, too.

Of course when the container offers random access iterators, one can leverage the iterator difference, but then you can't use the range-for:

std::vector<std::string> items;
for (auto it = items.begin(); it != items.end(); ++it)
  *it += std::to_string(it - items.begin());

Although I only show a C++11 example, I'm looking for hints for C++0x and C++98 as well.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • 4
    For insanity, `boost` has counting iterators and zip iterators: zip the count of elements with the range pf elements, iterate over that, and extract the item and index. Sadly the result is less than pretty. – Yakk - Adam Nevraumont Jul 31 '14 at 13:34
  • 2
    I don't know how "idiomatic" is [this answer to a similar question](http://stackoverflow.com/a/10962575/335858), but it is certainly very smart. – Sergey Kalinichenko Jul 31 '14 at 13:37
  • 15
    Go all Dr. Evil and do `for(auto z = std::make_pair(items.begin(), 0); z.first != items.end(); ++z.first, ++z.second) {}` – Captain Obvlious Jul 31 '14 at 13:37
  • @CaptainObvlious this should be an answer – Slava Jul 31 '14 at 13:40
  • @dasblinkenlight then probably this should be closed as a dupe – TemplateRex Jul 31 '14 at 13:45
  • @TemplateRex I am not sure about closing this one as a dupe, because the two questions look at the problem from different angles. This question asks about *keeping position* in the process of iterating, while the other one asks about *finding position* in the process of iterating with an iterator. I think that this question potentially allows for wider variety of answers, so I am somewhat reluctant to cast a closing vote here. – Sergey Kalinichenko Jul 31 '14 at 13:49
  • A small note, especially important since you care about idiomatic code: use `std::size_t` instead of `int` -- it expresses the intent better (non-negative size -- as opposed to an arbitrary / possibly negative integer), it is more established & idiomatic (both C and C++ std libs use it for counts, indexes, and sizes), and there's a guarantee that it's large enough to store sizes (the standard only guarantees that `int` can represent 16-bits-wide numbers, which for a signed type means values up to `32767`: en.cppreference.com/w/cpp/language/types#Integer_types). – Matt Aug 01 '14 at 14:17
  • Could you mark an acceptable answer? – TemplateRex Aug 29 '14 at 14:14
  • @TemplateRex No. They're all good. If you're desperate for points, I can give you a bounty :) – Kuba hasn't forgotten Monica Aug 29 '14 at 14:58
  • Na i just like to see accepted answers for future readers – TemplateRex Aug 29 '14 at 16:00

5 Answers5

30

My personal preference: just keep the extra index. It's clear as it is, and in case you ever have an if() inside the loop, you can also easily skip the count:

std::list<std::string> items;
{
    int i = 0;
    for (auto & item : items) {
        if (some_condition(item)) {
            item += std::to_string(i); // mark item as the i-th match
            ++i;
        }
    }
}

Just make sure to keep the i counter close near the loop, using extra { } to create a nested scope. Also, the post-increment is borderline unclear.

Alternatives: I would like a ranged-based index_for language construct that would provide an automatic counter i, but alas, that's not the case currently.

However, if you absolutely, positively, definitively insist on some nice wrapper, it's actually instructive to look at the semantics of your loop, which is that of a std::transform with a pair of std::list iterators and a boost::counting_iterator.

std::transform(
    begin(items), end(items), 
    boost::counting_iterator<int>(0), 
    begin(items), 
    [](auto const& elem, auto i) {
    return elem + std::to_string(i);    
});

This 4-legged overload of std::transform is sometimes called zip_with, which is why there are some comments to use a boost::zip_iterator with the list and counting_iterator.

You can make some nice range-based wrapper to be a lot more concise:

template<class Range, class T, class BinaryOp>
void self_transform(Range& rng, T value, BinaryOp op)
{
    auto i = value;
    for (auto& elem : rng) {
        elem = op(elem, i);        
        ++i;
    }
}

that can be more compactly called like:

self_transform(items, 0, [](auto const& elem, auto i) {
    return elem + std::to_string(i);    
});

Live Example.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • It's not bad, it's just irksome :) – Kuba hasn't forgotten Monica Jul 31 '14 at 13:47
  • 13
    @KubaOber well downvote all you want, but any `i` inside a loop is immediately clear for any reader, whereas some `std::distance` or zip iterator is needlessly obfuscated. – TemplateRex Jul 31 '14 at 13:48
  • 1
    @jrok I don't know, I can't see the problem of that little innocent `i`. Will defend at all cost (if nothing else, I'd get a Peer Pressure badge, yay!) – TemplateRex Jul 31 '14 at 13:50
  • 3
    I can remove the upvote if you desperately want that badge :P – jrok Jul 31 '14 at 14:02
  • I also prefer that version, even with an extra block scope if having `i` in the wild is a problem, rather than some complicated code that completely obfuscate what you're doing (esp. the zip iterators which bring back that `first`/`second` syntax that completely lose the reader and adds more typing to gain... what? An internalized index? With one `{}` around the loop + index, you get the same result) – JBL Jul 31 '14 at 14:27
  • @KubaOber Added some alternatives that you hopefully find less irksome ;-) – TemplateRex Jul 31 '14 at 20:10
10

Some compilers already offer expressions with lambda captures that'll be in C++1y standard. So you could do this:

#include <string>
#include <list>
#include <iostream>

int main()
{
    std::list<std::string> items {"a","b","c","d","e"};

    //                 interesting part here, initializes member i with 0, 
    //          ˇˇˇˇˇˇ type being deduced from initializer expression            
    auto func = [i = 0](auto& s) mutable { s+= std::to_string(i++); };
    for (auto& item : items) func(item);

    for (const auto& item : items) std::cout << item << ' ';
}

Output: a0 b1 c2 d3 e4

EDIT: For the record, I do think that having a little index variable outside of the scope of the loop is the best here (see other answers). But for fun, I wrote an iterator adaptor (with the help of Boost Iterator Adaptor) that you can use to tie a member function index to any iterator:

#include <boost/iterator/iterator_adaptor.hpp>
#include <list>
#include <string>
#include <iostream>
#include <algorithm>

// boiler-plate

template<typename Iterator>
class indexed_iterator
: public boost::iterator_adaptor<indexed_iterator<Iterator>, Iterator>
{
public:
    indexed_iterator(Iterator it, int index_value = 0)
    : indexed_iterator::iterator_adaptor_(it)
    , i_(index_value)
    { }

private:
    int i_;

    friend class boost::iterator_core_access;
    void increment(){ ++i_; ++this->base_reference(); }

    /* TODO: decrement, etc. */

public:
    int index() const { return i_; }
};

template<typename Iterator>
indexed_iterator<Iterator> make_indexed_iterator(Iterator it, int index = 0)
{
    return indexed_iterator<Iterator>(it, index);
}

// usuable code

int main()
{
    std::list<std::string> items(10);

    auto first = make_indexed_iterator(items.begin());
    auto last  = make_indexed_iterator(items.end());
    while (first != last) {
        std::cout << first.index() << ' ';
        ++first;
    }
}

Output: 0 1 2 3 4 5 6 7 8 9

jrok
  • 54,456
  • 9
  • 109
  • 141
  • 4
    Ahh, that little `func(item)` call. So innocent-looking, yet it mutates both `func` and `item`. I hope that doesn't become idiomatic… – John Calsbeek Jul 31 '14 at 14:16
6

I would probably end up with something like this:

std::list<std::string> items = ...;

{
    int index = 0;
    auto it = items.begin();
    for (; it != items.end(); ++index, ++it)
    {
        *it += std::to_string(index);
    }
}

I've seen way more uses of for loops with two loop variables than I have seen uses of zipped iterators or lambda capture counted variables. "Idiomatic" is a subjective term, but I would call this idiomatic.

Having an explicit extra variable makes it obvious that we are just counting upward. This is important if you decide to do anything non-trivial in the loop. For example, you can insert or remove an item in the list and adjust the index accordingly—if you were using an iterator adapter, it might not even be obvious that the index it provides might not actually be the index of the item in the container.


Alternately, you could write a variant of std::for_each:

template <typename InputIt, typename BinaryFn>
BinaryFn for_each_index(InputIt first, InputIt last, BinaryFn fn)
{
    for (int i = 0; first != last; ++i, ++first)
    {
        fn(i, *first);
    }
    return fn;
}

which at least isn't obfuscated. Then you can do this:

std::list<std::string> items = ...;
for_each_index(items.begin(), items.end(), [](int i, std::string& s)
{
    s += std::to_string(i);
});
John Calsbeek
  • 35,947
  • 7
  • 94
  • 101
5

Well using Boost.Range you can do this:

std::list<std::string> items;
for (const auto & t : boost::combine(items, boost::irange(0, items.size()))) 
{
    std::string& item = boost::get<0>(t);
    int i = boost::get<1>(t);
    item += std::to_string(i);
}
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
3

There is a little library called pythonic, which gives you the enumerate() function, you might know from python, in C++. It creates a list of pairs with index and value. You then iterate over this list instead. It let you do the following (from their documentation):

#include <vector>
#include <iostream>
#include "pythonic/enumerate.h"
using namespace pythonic;

// ...

typedef std::vector<int> vec;

for (auto v : enumerate(vec {0, -1337, 42}))
{
    std::cout << v.first << " " << v.second << std::endl;
}

// ...

Which gives you as output

$ ./enumerate
0 0
1 -1337
2 42
$
hildensia
  • 1,650
  • 2
  • 12
  • 24