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.
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.
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
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);
});
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);
}
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
$
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With