tags:

views:

230

answers:

8

Hi all,

I've been discussing the use of size_t with colleagues. One issue that has come up is loops that decrement the loop variable until it reaches zero.

Consider the following code:

for (size_t i = n-1; i >= 0; --i) { ... }

This causes an infinite loop due to unsigned integer wrap-around. What do you do in this case? It seems far to easy to write the above code and not realise that you've made a mistake.

Two suggestions from our team are to use one of the following styles:

for (size_t i = n-1; i != -1 ; --i) { ... }

for (size_t i = n; i-- > 0 ; ) { ... }

But I do wonder what other options there are...

Cheers,

Dan

+5  A: 
  1. Replace the loop with an algorithm.
  2. Use a reverse iterator instead of an integer.
  3. Count down from n to 1, but inside the loop use i-1 instead of i.
Jerry Coffin
+3  A: 

Are you using standard library containers? If so I like reverse_iterator

   vector<int> ivect;

   // push, push, push...

   vector<int>::reverse_iterator riter;
   for(riter=riter.rbegin(); riter!=ivect.rend(); ++riter)
   {
       //...
   }

For a raw array you can just use a std::reverse_iterator the key to this is that a pointer is an iterator:

int i[] = {1, 2, 3, 4};

typedef std::reverse_iterator<const int*> irevit;

irevit iter(i+4);
irevit end(i);
for(; iter != end; ++iter) {
    cout << *iter;
}

// Prints 4321

Noncontiguous object iteration can be done by storing the object pointers in a container or array:

struct Foo {
    Foo(int i) I(i) { }
    int I;
}

vector<Foo*> foos;
for(int i = 0; i < 10; ++i)
    foos.push_back(new Foo(i));

typedef vector<Foo*>::const_reverse_iterator frevit;

frevit iter(foos.rbegin());
for(; iter != foos.rend(); ++iter) {
    cout << (*iter)->I;
}

// Prints 9876543210

If you really want to use a naked size_t then why use all of this implicitly confusing -1 trickery in the other answers? The max value of size_t is explicitly available to use as your termination value:

int is[] = {1, 2, 3, 4};
int n = 3;

for (size_t i = n; i != std::numeric_limits<size_t>::max(); --i) {
    cout << is[i] << endl;
}

// prints 4321
joshperry
Yes we use STL containers - what about when it's not an STL container that you're dealing with?
Daniel Paull
@DanielPaull: `vector<int>::reverse_iterator` is just `std::reverse_iterator<vector<int>::iterator>`. Any time that you have a type that models the Bidirectional Iterator concept (e.g.: a pointer type), you can use `std::reverse_iterator`. The implementation of `std::reverse_iterator` is basically suggestion 3 of Jerry Coffin's answer ("Count down from `n` to 1, but inside the loop use `i-1` instead of `i`.")
Daniel Trebbien
@Daniel You can use std::reverse_iterator with a carray pretty easily since a pointer is an iterator, I added that to my answer.
joshperry
@joshperry - that's a better answer. Now, what if you're not iterating through memory?
Daniel Paull
@Daniel Can you give an example of what you mean?
joshperry
Sure - say that you're accessing an api that has "getFoo( unsigned int i )" and you want to enumerate all of the Foos in reverse order. So, i is not some offest from a base pointer, but rather, a parameter to an function call.
Daniel Paull
Well, if you're Foos are in a contiguous layout (in an array) you can still use pointer arithmetic and a `std::reverse_iteratro<Foo*>`. But if they aren't contiguous in memory and aren't in a standard container then this method probably isn't going to work for you. Though you could keep an array or vector of `Foo*` to keep a contiguous lookup table and use a `std::reverse_iterator<Foo**>` and dereference the iterator twice.
joshperry
+2  A: 

i != -1 relies on the -1 being silently cast to a size_t, which seems fragile to me, so, of the alternatives you present, I'd definitely go with the post-decrement one. Another possibility (esp. if you don't actually need i in the loop body but just need to iterate on an array in reverse order) would be to wrap the array in a std::-like container and use an iterator on the wrapper, with the rbegin and rend methods. E.g., Boost.Array would support the latter choice.

Alex Martelli
The "silent cast" is really an implicit conversion, which is a defined part of the language. It's not at all fragile.
caf
But how is the conversion _certain_ to be _exactly_ to a `size_t`, as opposed to an `unsigned int`? I believe all you know *for sure* is that `size_t` is at least 16 bit -- but is it _ensured_ that it's also at least as large as an `unsigned int`? Otherwise (if `size_t` is `unsigned short`, 16 bit, and `unsigned int` is 32 bit), `i != -1` may be "always false" (`gcc` warns about that if you `typedef unsigned short size_t`, and are you _sure_ no compiler is _ever_ allowed to do that no matter what the provocation?). Needs "standards lawyering" at least -- that's _not_ "not at all fragile".
Alex Martelli
The conversion isn't necessarily to `size_t`. First integer promotion happens. If `size_t` is actually smaller than an `int` (i.e. an `int` can hold the large positive number that decrementing a `size_t` with value 0 results in) then both sides of the comparison would remain `int` with the comparison obviously always failing. Only if a `size_t` is at least as wide as an `int` and you're not on an architecture where the signed types can hold all the values of the corresponding unsigned types will `-1` be converted to an unsigned type of the correct width. IMHO an explicit conversion is needed.
Charles Bailey
@Charles, thanks for the analysis! My point is, even if a "standards lawyer" could prove by a subtle reading of some obscure subclause that, per the standard, it's "supposed to" work (and more so of course if you're right and it's _not_ supposed to work;-), the construct is **fragile**: since there are clear alternatives that don't rely on such delicate subtleties, the alternatives should therefore be preferred, aiming at _solidity_ (with clarity and maintainability being part of that goal) at the heart of our code.
Alex Martelli
Good points all. The possibility of `size_t` being promoted to `int` by the integer promotions certainly exists (16 bit `size_t` with 32 bit `int` are allowed). I do not agree though with @Charles Bailey in the general case where signed types cannot hold all the values of the unsigned type with the same conversion rank (the usual case) - in that case (assuming the integer conversions haven't already promoted `size_t` to `int`) the arithmetic conversions convert the signed operand to unsigned.
caf
+1  A: 

Here is a pointer to a good discussion on this topic.

I would try:

for( size_t i = n; i != 0; i-- ) {
  // do stuff with array[ i - 1 ]
}
ArunSaha
+1  A: 
size_t i = n-1;

do  { 
  ...
} while ( i-- != 0);

You may wrap that with a if (n > 0) if necessary.

ring0
+2  A: 

If you're worried about accidentally writing a loop like that, some compilers will warn about such things. For example, gcc has a warning enabled by the -Wtype-limits option (also enabled by -Wextra):

x.c:42: warning: comparison of unsigned expression >= 0 is always true
caf
+1  A: 

Unsigned integers are guaranteed to wrap around nicely. They just implement arithmetic modulo 2N. So an easy to read idiom is this one:

for (size_t i = n-1; i < n ; --i) { ... }

this sets the variable to the initial value that you want, shows the sense of the iteration (downward) and gives precisely the condition on the values that you want to handle.

Jens Gustedt
+1  A: 

Personally I have come to like:

for (size_t i = n; i --> 0 ;)

It has a) no funny -1, b) the condition check is mnemonic, c) it ends with a suitable smiley.

visitor
Dude, that rocks!
Daniel Paull