views:

335

answers:

4

I frequently find myself requiring to iterate over STL vectors. While I am doing this I require access to both the vector element and its index.

I used to do this as:

typedef std::vector<Foo> FooVec;
typedef FooVec::iterator FooVecIter;

FooVec fooVec;
int index = 0;
for (FooVecIter i = fooVec.begin(); i != fooVec.end(); ++i, ++index)
{
    Foo& foo = *i;
    if (foo.somethingIsTrue()) // True for most elements
        std::cout << index << ": " << foo << std::endl;
}

After discovering BOOST_FOREACH, I shortened this to:

typedef std::vector<Foo> FooVec;

FooVec fooVec;
int index = -1;
BOOST_FOREACH( Foo& foo, fooVec )
{
    ++index;
    if (foo.somethingIsTrue()) // True for most elements
        std::cout << index << ": " << foo << std::endl;
}

Is there a better or more elegant way to iterate over STL vectors when both reference to the vector element and its index is required?

I am aware of the alternative: for (int i = 0; i < fooVec.size(); ++i) But I keep reading about how it is not a good practice to iterate over STL containers like this.

+8  A: 
for (size_t i = 0; i < vec.size(); i++)
    elem = vec[i];

Vectors are a thin wrapper over C arrays; whether you use iterators or indexes, it's just as fast. Other data structures are not so forgiving though, for example std::list.

iconiK
Actually this is not necessarily the best. Random access is less prone to cache optimization then iteration -- iteration carries the additional information that data will be accessed sequentially what allows modern optimizers to prepare the cache and save us from the oh-so-costly cache misses. Run a test comparing non-trivial vector iteration on different architectures -- you might be surprised.
Kornel Kisielewicz
That is interesting, but can you provide a source (link or keywords)? For which compiler is that the case? Generally recognizing sequential access in loop shouldn't be harder for compiler than recognizing std iterators. I can think of couple other reasons to explain why iteration can be faster, depending on vector implementation.
ima
In fact, I just checked Intel compiler documentation and, while it lists plenty of cache access optimizations for loops, there is not a single mention (under any name) of stl flavor of iteration. In the best case, stl iteration will be optimized into loop, then the same techniques will apply.
ima
Actually assuming sequential access with iterators is not possible as I can easily advance by any number of element I want with a random access iterator. It is no more optimizable than an index; a vector iterator is practically a plain pointer. Whether you increment the pointer each iteration or you add i to it, it's still a simple add instruction.
iconiK
+6  A: 

You can always compute the index in the loop:

std::size_t index = std::distance(fooVec.begin(), i);

For a vector, this is quite likely to be implemented as a single pointer subtraction operation, so it's not particularly expensive.

James McNellis
However, for other data structures it can get quite expensive.
iconiK
@iconiK: I don't disagree with that.
James McNellis
@James: I think it's better to increment the index in the loop if you iterate over all the elements of a generic container. That way you don't loop around a list 3 million times calculating an index.
iconiK
@iconiK: Accessing an element via subscript should not be faster than computing the distance from the beginning of the container to a given iterator. There might be corner cases, but I can't think of any off the top of my head.
James McNellis
`std::distance` for *random access iterators* like `std::vector` is O(1), being just a pointer subtraction, but for other less flexible iterators (e.g. those of `std::list`) it's an O(N) operation, and it is better to not compute `index` again and again in the loop.
KennyTM
+4  A: 

Elegance is in the eye of the beholder, however do remember pointer/iterator arithmetics :)

for (FooVecIter i = fooVec.begin(); i != fooVec.end(); ++i)
{
    Foo& foo = *i;
    if (foo.somethingIsTrue()) // True for most elements
        std::cout << i - fooVec.begin() << ": " << foo << std::endl;
}

The up-side compared to the distance method is that you won't mistakenly do this for a non-random_access_iterator, so you'll always be in O(1).

Kornel Kisielewicz
You mean i = fooVec.begin(); right?
iconiK
@iconiK, sure I do, thanks.
Kornel Kisielewicz
Why you you subtracting a FooVecIter from a Foo in i - fooVec.begin() // makes no sense.
iconiK
Okay, it works, though I don't understand how exactly, as it's weird (but then, I'm no iterator wizard): http://codepad.org/ycqlJxU5
iconiK
@iconiK, both are FooVectIter
Kornel Kisielewicz
@Kornel: I agree; enforcing that the iterator is random-accessible is often a better idea than using distance.
James McNellis
@Kornel, ahh, my bad, I thought I saw foo - fooVec.begin(0). Being tired is not good for programming, I tell you that!
iconiK
+1  A: 

For the specific question:

Is there a better or more elegant way to iterate over STL vectors
when both reference to the vector element and its index is required?

IMHO,

for( size_t i = 0; i < fooVec.size(); ++i ) {
    Foo & foo = fooVec[ i ];
}

is the most simple and elegant solution. Per the requirements of the question, using an iterator is not required.

I know iterators are there, they are useful, and using them is the preferred approach. But, we also have to put the solution after the problem (as in putting the carriage after the horse, nor the other way). I don't see how something becomes elegant just by using iterator, at least for this problem. But, I am not an expert, so I look forward what others have to say.

ArunSaha
You can't rebind a reference.
Matt Curtis
Yes, corrected.
ArunSaha