75

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...

cigien
  • 57,834
  • 11
  • 73
  • 112
Daniel Paull
  • 6,797
  • 3
  • 32
  • 41
  • 3
    Looks like a dup: http://stackoverflow.com/questions/665745/whats-the-best-way-to-do-a-reverse-for-loop-with-an-unsigned-index. This didn't come up in my initial searches. – Daniel Paull Sep 02 '10 at 02:02
  • See also: http://stackoverflow.com/questions/3484474/for-loop-condition-to-stop-at-0-when-using-unsigned-integers – CB Bailey Sep 02 '10 at 06:50
  • Those are both tagged 'C' only, doing this in C++ has some very clear alternatives that are not available in C. – joshperry Sep 02 '10 at 14:30
  • 1
    @joshperry: I didn't say duplicate or vote to close as a duplicate I said "See also". This question is tagged C _and_ C++ and the linked question is applicable to C++ as well, so I think that the link is valuable. – CB Bailey Sep 03 '10 at 07:18
  • I've removed the C tag, as there is a separate question asking the same thing for that language https://stackoverflow.com/questions/665745/whats-the-best-way-to-do-a-reverse-for-loop-with-an-unsigned-index – cigien Oct 31 '20 at 14:19

11 Answers11

114

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
  • 8,564
  • 2
  • 26
  • 15
  • 32
    One disadvantage is that i starts at n-1 (not n), which is visually non-obvious – Diogenes Creosote Apr 02 '15 at 21:01
  • 7
    [What is the name of the “-->” operator?](http://stackoverflow.com/q/1642028/995714) – phuclv Jul 21 '16 at 16:07
  • 2
    that is no operator, its unary postfix -- followed by binary > i.e. decrement this thing store the result and compare the original value with whatever is on the right of the > – jheriko Jan 17 '17 at 18:08
  • 7
    As @phuclv pointed out, newcomers to C++ could be extremely confused by the above notation. I would far prefer to see this as: `for (size_t i = n; i-- > 0; )`. – Parker Nov 19 '18 at 14:44
69

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
  • 76,821
  • 6
  • 102
  • 177
  • 4
    I think this is the best answer – gd1 Apr 12 '15 at 22:16
  • 3
    TIL that unsigned reverse iteration is not so bad. – TemplateRex Jul 30 '15 at 21:50
  • 6
    Although this works, it really looks like a bug and would fail if someone changed the type to signed. I'd recommend to anyone who uses this that they add plenty of warnings in comments as a courtesy to any developers who inherit the code. – Parker Nov 19 '18 at 14:48
13
  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
  • 476,176
  • 80
  • 629
  • 1,111
5

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
  • 41,167
  • 16
  • 88
  • 103
  • Yes we use STL containers - what about when it's not an STL container that you're dealing with? – Daniel Paull Sep 02 '10 at 01:54
  • @DanielPaull: `vector::reverse_iterator` is just `std::reverse_iterator::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 Sep 02 '10 at 02:02
  • 1
    @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 Sep 02 '10 at 02:07
  • @joshperry - that's a better answer. Now, what if you're not iterating through memory? – Daniel Paull Sep 02 '10 at 02:15
  • @Daniel Can you give an example of what you mean? – joshperry Sep 02 '10 at 02:40
  • 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 Sep 02 '10 at 02:43
  • Well, if you're Foos are in a contiguous layout (in an array) you can still use pointer arithmetic and a `std::reverse_iteratro`. 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` and dereference the iterator twice. – joshperry Sep 02 '10 at 03:05
5

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
  • 233,326
  • 40
  • 323
  • 462
4

From C++20, you can use ranges, and views, like this:

namespace sv = std::views;
    
for (unsigned i : sv::iota(0u, n) | sv::reverse)
    std::cout << i << "\n";  

Here's a demo.

The code is very readable, and it completely avoids any issues with unsigned wrap-around behavior, since i only has values in the range [0,n).

cigien
  • 57,834
  • 11
  • 73
  • 112
3

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
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 3
    The "silent cast" is really an implicit conversion, which is a defined part of the language. It's not at all fragile. – caf Sep 02 '10 at 04:29
  • 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 Sep 02 '10 at 05:05
  • 2
    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. – CB Bailey Sep 02 '10 at 06:40
  • 1
    @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 Sep 02 '10 at 14:19
  • 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 Sep 03 '10 at 02:34
  • Alternatively, use `SIZE_MAX` (C99/C++11) or `std::numeric_limits::max()` (C++98) – ThatsJustCheesy Mar 22 '18 at 23:59
2
size_t i = n-1;

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

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

Déjà vu
  • 28,223
  • 6
  • 72
  • 100
1

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 ]
}
Arun
  • 19,750
  • 10
  • 51
  • 60
1

Yet another way (no signed/unsigned comparisons):

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

since

(i + 1 > 0) === (i > -1)
boni
  • 253
  • 4
  • 7
1

Another solution (available on POSIX compliant systems) that I found to be simple and effective is to replace size_t with ssize_t:

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

On non-POSIX systems, ssize_t is not difficult to typedef: Alternative to ssize_t on POSIX-unconformant systems

Corentor
  • 661
  • 1
  • 6
  • 11