Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which type to use when iterating a std::vector (without iterators)?

Tags:

c++

Perhaps this question is trivial, but thinking a second time about it I wonder how to do the following really the right way:

std::vector<K> v = ...;
for(T i=0; i<v.size(); ++i) {
  const K& t = v[i];
  // use t *and i*
}

What type should T be? int, unsigned int, int32_t, size_t (which would be the type of v.size()) or any other suggestions? Please try to consider portability, error-proneness and performance and be objective in your answer.

Edit: I did not choose iterators, because it would also like to use the index number i explicitly.

like image 334
Danvil Avatar asked Jul 27 '10 09:07

Danvil


3 Answers

The type of i should be the same as the return value of size(), which is std::vector<K>::size_type. However, in practice, size_t will do just fine. If you use a signed integer type, then your compiler will probably warn about a signed/unsigned mismatch in the less-than comparison.

Normally you would use an iterator for this anyway:

std::vector<K> v = ...;
for (std::vector<K>::iterator i = v.begin(); i != v.end(); ++i) {
  const K& t = *i;
  // use t
}

Or, in C++0x:

std::vector<K> v = ...;
for (auto i = v.begin(); i != v.end(); ++i) {
  const K& t = *i;
  // use t
}

In response to your comment about using the vector index with iterators, consider the std::distance() function which is constant time operation for vector iterators:

std::vector<K> v = ...;
for (auto i = v.begin(); i != v.end(); ++i) {
  const K& t = *i;
  size_t index = std::distance(v.begin(), i);
  // use t and index
}
like image 163
Greg Hewgill Avatar answered Oct 01 '22 16:10

Greg Hewgill


The type returned by v.size and expected by v[] is std::vector<K>::size_type.

However, consider using iterators instead : the risk of going out of bounds is lower, and you can use the standard library algorithms.

like image 26
Victor Nicollet Avatar answered Oct 01 '22 16:10

Victor Nicollet


The type T usually doesn't matter as long as it is as large as the vector size.

In terms of performance, there should't be any differences.

In terms of error-proneness/portability, I suggest using signed integers, because if you somehow subtract values from i, you'll get a large positive number, which is harder to check than a negative number. But because v.size() is a size_t, if you use a signed integer for T, and v has ≥ 231 (or 263) items, the i will have to become invalid before ending. But I think if the vector needs to be that ridiculously large (esp. on 64-bit platforms), there is other a bigger problem than which type T to choose.

Never use int32_t for a counter.


(These are before OP's edit:)

But in your case you may want iterate with an iterator instead.

for (std::vector<K>::const_iterator cit = v.begin(); cit != v.end(); ++ cit) {
  const K& t = *cit;
  // ...
}

With Boost.Foreach you could turn this mess into

BOOST_FOREACH(const K& t, v) {
  // ...
}

In C++0x this becomes a built-in feature (§[stmt.ranged]), but AFAIK no compilers supported it yet:

for (const K& t : v) {
  // ...
}

But most compilers that claim to support C++0x should allow auto:

for (auto cit = v.cbegin(); cit != v.cend(); ++ cit) {
   const K& t = *cit;
   // ...
}

or lambda expression:

std::for_each(v.cbegin(), v.cend(), [](const K& t) { 
   ...
});
like image 40
kennytm Avatar answered Oct 01 '22 18:10

kennytm