Erm... just because you can, doesn't mean you should.
Also, what if you want to go down to something other than 0?
Erm... just because you can, doesn't mean you should.
Also, what if you want to go down to something other than 0?
Agreed, seeing that example briefly made me consider whether this blog post was a parody. Sure, it works for this exact example, by relying on i wrapping "down" to MAX_INT on the last iteration. But how long will it take the next developer who works on the code base to figure that out? Will they figure it out before or after committing changes that break it? Or worse yet, before or after shipping code?
for(size_t i = 0; i < size; i++){
// loop body
}
for(size_t i = size; i > 0;){ i--;
// loop body
} size_t i = size;
while(i-- > 0){
// loop body, possibly break
}
use(i); // i wraps below 0
size_t i = size;
while(i > 0){
i--;
// loop body, possibly break
}
use(i); // i doesn't wrap
Practically, i leaves the for-loop scope, so most never encounter this problem. for (size_t i = size - 1; i >= x; i--)The stopping condition is incredibly confusing and non-obvious. Misleading at first glance, in fact. The whole thing is so unidiomatic that I don't think I've even seen it once in my life. It's a better contender for an underhanded C++ code contest than production code.
> but if you want to count down to x instead of 0 you just do i >= x
No you can't. That fails if x == 0. Which perfectly illustrates why using unsigned everywhere isn't so great. And I say this as someone who likes unsigned types and uses them more than average!
https://en.wikipedia.org/wiki/Loop_invariant
You should think of it as the condition that's true for all iterations, not a one-time event that halts the loop. The loop is short for this:
for(size_t i = size - 1; 0 <= i && i < size; i--){
}
Which works for both signed and unsigned numbers. It just so happens that for unsigned numbers you can omit the left-hand side of the &&, and for signed numbers you can omit the right-hand side. To support arbitrary lower bounds, you omit neither.(a) It's "a" condition that holds true for all iterations, not "the" condition. Plenty of other conditions can hold true across the iterations of any given loop too. In fact the one interesting thing about the loop invariant compared to any other conditions is the very fact that it is guaranteed to cease to hold immediately after the loop, assuming you don't break in the loop. Other conditions can still continue to hold. i.e. The stopping condition is the entire point of the loop invariant.
(b) I'm well aware what a loop invariant is; I've worked on compilers. I am also a human. Humans care about when a loop starts and stops. There's nothing backwards about it, it's the most straightforward way people think of loops. And it's literally why more modern languages have introduced better syntaxes that merely spell the boundaries and/or values, and skip spelling the invariants entirely.
Exactly! That's precisely the problem with it.
(Hint: think about your code when size = 0.)
for (size_t i = size; i—- > x;)(Of course the best thing is real range types like in Rust.)