47

I came across a set of slides for a rant talk on C++. There were some interesting tidbits here and there, but slide 8 stood out to me. Its contents were, approximately:

Ever-changing styles

Old and busted:

for (int i = 0; i < n; i++) 

New hotness:

for (int i(0); i != n; ++i) 

I'd never seen a for loop using the second form before, so the claim that it was the "New hotness" interested me. I can see some rationale for it:

  1. Direct initialization using a constructor vs copy initialization
  2. != could be faster in hardware than <
  3. ++i doesn't require the compiler to keep the old value of i around, which is what i++ would do.

I'd imagine that this is premature optimization, though, as modern optimizing compilers would compile the two down to the same instructions; if anything, the latter is worse because it isn't a "normal" for loop. Using != instead of < is also suspicious to me, because it makes the loop semantically different than the original version, and can lead to some rare, but interesting, bugs.

Was there any point where the "New hotness" version of the for loop was popular? Is there any reason to use that version these days (2016+), e.g. unusual loop variable types?

26
  • 52
    ++i is better than i++ not for any arcane reasons of second-guessing the compiler, but because it's what you mean. Write code that does what you mean, not some other code that happens to solve your problem as a side effect. But that's been true since 1972, so I wouldn't call that "new hotness". Commented Sep 14, 2016 at 15:37
  • 19
    Your "new hotness" compiles in c++03, I would disregard the content of those slides. Commented Sep 14, 2016 at 15:38
  • 7
    A similar argument can be made for the predicate. Use < when the ordering of integers is material to the algorithm. Otherwise, if all you want is a sentinel that signals the end of the iteration, then != is more appropriate, since it makes fewer assumptions about the details and concentrates on the underlying logic. The benefit is that the very thought process is now "portable", since it applies to a much more general concept of iteration. Again, the technicalities haven't changed since 1972, but the experience with generality and code reuse has. Commented Sep 14, 2016 at 15:41
  • 8
    @KerrekSB - But I don't mean to obtain either its previous or its new value - all I'm interested in is the side-effect of incrementing ;) Commented Sep 14, 2016 at 15:43
  • 16
    This is the same guy who delivered a massive rant on GCC's treating signed overflow as undefined. A healthy helping of salt is needed. Commented Sep 14, 2016 at 19:47

7 Answers 7

75
  1. Direct initialization using a constructor vs copy initialization

    These are exactly identical for ints and will generate identical code. Use whichever one you prefer to read or what your code policies are, etc.

  2. != could be faster in hardware than <

    The generated code won't actually be i < n vs i != n, it'll be like i - n < 0 vs i - n == 0. That is, you'll get a jle in the first case and a je in the second case. All the jcc instructions have identical performance (see instruction set reference and optionization reference, which just list all the jcc instructions together as having throughput 0.5).

    Which is better? For ints, probably doesn't matter performance-wise.

    Strictly safer to do < in case you want to skip elements in the middle, since then you don't have to worry about ending up with an infinite/undefined loop. But just write the condition that makes the most sense to write with the loop that you're writing. Also take a look at dasblinkenlight's answer.

  3. ++i doesn't require the compiler to keep the old value of i around, which is what i++ would do.

    Yeah that's nonsense. If your compiler can't tell that you don't need the old value and just rewrite the i++ to ++i, then get a new compiler. Those definitely will compile to the same code with identical performance.

    That said, it's a good guideline to just use the right thing. You want to increment i, so that's ++i. Only use post-increment when you need to use post-increment. Full stop.


That said, the real "new hotness" would definitely be:

for (int i : range(n)) { ... } 
Sign up to request clarification or add additional context in comments.

13 Comments

@Nawaz lol you deleted your post like 1s before I was about to upvote it...
For your proposed newest hotness. How about auto i? What type would i be if auto was used?
1 -- Right, I know they compile down to the same thing for primitives, but I was wondering whether there was an advantage for any other types, or whether the compiler can still figure things out because of the context.
And I should be ashamed of myself for missing that reasoning for 2. I finished a low-level programming class a few semesters ago...
@awksp They're always identical, except in cases where the constructor is explicit in which case one will compile and the other won't.
|
22

You are right about optimizing compilers and prefix vs. postfix ++ operator. It does not matter for an int, but it matters more when you use iterators.

The second portion of your question is more interesting:

Using != instead of < is also suspicious to me, because it makes the loop semantically different than the original version, and can lead to some rare, but interesting, bugs.

I would rephrase it as "can catch some rare, but interesting, bugs."

One simple way to argue this point was offered by Dijkstra in his book A Discipline of Programming. He pointed out that it is easier to reason about loops with stronger post-conditions than it is to reason about loops with weaker post-conditions. Since post-condition of a loop is the inverse of its continuation condition, one should prefer loops with weaker continuation conditions.

a != b is weaker than a < b, because a < b implies that a != b, but a != b does not imply that a < b. Therefore, a != b makes a better continuation condition.

In very simple terms, you know that a == b immediately after the loop with a != b is over; on the other hand, when the loop with a < b is over, all you know is that a >= b, which is not as good as knowing the exact equality.

15 Comments

That's pretty academic, though - if you're ever in a situation where a > b would lead to post-loop problems, then the continuation condition a != b would already have led to an infinite loop.
@OliverCharlesworth That's exactly the point! An infinite loop has a better chance of attracting your attention than a (slightly) incorrect result of a computation. I think that this specific argument is one of these rare cases when a purely academic argument gives you free, easy-to-follow advise for everyday coding. FWIW, I've been writing loops this way for some 28 years.
it matters more when you use iterators -- It's a sure sign of my inexperience that I didn't think about iterators at all when writing this question. Dijkstra's argument is a fresh one to me. I'm not totally convinced, because while you know that a == b immediately after the loop, you don't know that a was "within range", for lack of a better phrase. And if you can prove that the loop index can't skip over the value of b, shouldn't that also be applicable to a < b? Can't argue that an infinite loop is more noticeable than an off-by-one error
@awksp When you use !=, your readers wouldn't have to prove that a wouldn't skip over b, because you wrote it explicitly in the body of your code. When you write <, on the other hand, your readers might need to check the body of your loop for other tricks, such as setting a to MAX_INT or something equally silly. Using != tells them that there are no tricks; < leaves the readers to check for themselves.
1. There's always another possible way to get out of a loop (which will make reasoning about the post-conditions a lot harder): break, return and goto 2. In many cases, you don't care about the value of a after the loop - the loop variable is often scoped. 3. a < b and a != b are only logically equivalent when you're increasing a in steps of c and can make the guarantee that the start value will be lower than the end value and the difference b-a will be a multiple of c (so your stronger post-condition needs a stronger guarantee on the loop parameters).
|
13

I personally don't like the second one, I would use:

for (int i = 0; i < n; ++i); //A combination of the two :) 

int i = 0 vs int i(0)

No difference whatsoever, they even compile to the same assembly instructions (no optimizations):

int main() { int i = 0; //int i(0); } 

int i = 0 version:

main: pushq %rbp movq %rsp, %rbp movl $0, -4(%rbp) movl $0, %eax popq %rbp ret 

int i(0) version:

main: pushq %rbp movq %rsp, %rbp movl $0, -4(%rbp) movl $0, %eax popq %rbp ret 

i < n vs i != n

You are right that the != might introduce some interesting bugs:

for (int i = 0; i != 3; ++i) { if (i == 2) i += 2; //Oops, infinite loop //... } 

The != comparison is mostly used for iterators, which don't define a < (or >) operator. Maybe that's what the author meant?

But here, the second version is clearly better, as it more clearly states intent than the other one (and introduces less bugs).


i++ vs ++i

For built-in types (and other trivial types), such as int, there is no difference, as the compiler will optimize the temporary return values out. Here, again, some iterators are expensive, and so the creation and destruction might be a performance hit.

But that really doesn't matter in this case, as even without optimizations they emit the same assembly output!

int main() { int i = 0; i++; //++i; } 

i++ version:

main: pushq %rbp movq %rsp, %rbp movl $0, -4(%rbp) addl $1, -4(%rbp) movl $0, %eax popq %rbp ret 

++i version:

main: pushq %rbp movq %rsp, %rbp movl $0, -4(%rbp) addl $1, -4(%rbp) movl $0, %eax popq %rbp ret 

4 Comments

What about int i{0} in the first section of your answer? Will it be different for non-primitive iterator type?
@Sergey Yes and no, I don't think so. They all both call the constructor (except for {}, which would perform aggregate initializing).
What about different compilers?
@M.M I used gcc, but the result will likely be the same for clang and msvc.
7

The two forms are in no way related to performance. What matters is how you write code in general. Follow similar patterns and focus on expressiveness and conciseness. So for the initialisation, prefer int i(0) (or better: i{0}) as this underlines that this is an initialisation, not an assignment. For the comparison, the difference between != and < is that you put lower requirements on your type. For integers there is no difference, but in general iterators might not support less than, but should always support equality. Finally, prefix increment expresses your intent better because you do not use the result.

2 Comments

+1 for i{0} - this is the real new hotness: all initialization everywhere has the same look now, and is visibly different from assignment.
The curly initialisation is not just new hotness. It is better because you avoid narrowing conversions to take place.
3

In this code it makes no difference. But I guess what the writer is aiming for is to use the same style coding style for all of the for loops (except range-based ones, presumably).

For example if we had some other class as the loop counter type

for ( Bla b = 0; b < n; b++ ) 

then there are problems:

  • Bla b = 0; may fail to compile if Bla does not have an accessible copy/move constructor
  • b < n may fail to compile if Bla does not admit definiig a weak ordering, or define operator<.
  • b++ may fail to compile or have unintended side-effects, since post-increment typically returns by value

Using the writer's suggested pattern, the requirements on a loop iterator are lessened.


Note that this discussion can go on forever. We could say int i{}; is better because some types might not admit 0 as initializer. Then we could say, well what if n was not an int? It should really be decltype(n) i{}. Or in fact we should use a range-based loop which fixes all the above problems. And so on.

So at the end of the day it is still personal preference.

1 Comment

If an iterator implements postfix operator++, it needs to work correctly when used solely for its side effects.
3

i != n

Frankly, slide 8 lost me right there, and you are correct with the suspicion that something might not be quite right.

Aside from the high likelyhood that a modern compiler will have those kinds of loop optimized as thoroughly as it is theoretically possible for current CPUs, encouraging a programmer to write less robust code to "help the optimizer" for whatever reason is just very oldfashioned and has no place at all in the modern world, IMO. The real cost of software is the human time these days, not the CPU time, for 99.99...% of all projects.

On a meta-level, truisms have no place in coding guidelines. A slide establishing a coding convention without giving objective and well-thought-out reasons for it is useless. Note that I would be OK with a reason like "we do it this way because we want to pick one style instead of a plethora", I don't need a technical reason - just any reason. I can then decide whether I accept the reasoning behind the coding guideline or not (and thus accept the guideline itself).

Comments

0

It is stylistically better to use postfix increment/decrement in C-style for loops whenever this is possible,

for (i = 0; i < n; i++) 

rather than

for (i = 0; i < n; ++i) 

With a postfix operator, the loop variable i appears on the left-hand side of all three expressions, which makes the code easier to read. Also, if you need to step by any value other than 1, you have to write it with the i on the left ...

for (i = 0; i < n; i += 2) 

so for consistency's sake it should also be done that way when you are using the ++ shorthand for += 1. (As pointed out in the comments, we might find it easier to read += 1 than ++ if we didn't have 45 years of convention teaching us ++.)

With fundamental types like int, prefix/postfix increment will make no performance difference; as other answers have demonstrated, the compiler will generate exactly the same code either way. With iterators, the optimizer has to do more work to get it right, but I don't think there's any excuse for a C++11-era compiler that fails to handle postfix operator++ as efficiently as prefix operator++ when they are used solely for their side effects.

(Of course, if you're stuck with an iterator that only supports prefix ++, then you write it that way.)

6 Comments

With that logic, why would it not be more consistent and more readable to always do i += 1?
@CodyGray Only because it is traditional to use the increment operators. If we were starting from scratch, we would find it more consistent and readable to write i += 1 or i -= 1.
There is a huge difference between I += 1 and ++I. For += 1 you need random access iterators, whereas you for ++ only need forward iterators. I know you are talking integer operations here, but the general rule of thumb should be to not use more than the minimal set of requirements needed for your algorithm.
@user3721426 If it had been customary to write for (i = 0; i < N; i += 1) then forward iterators would have been required to support operator += with argument 1.
How would you implement += 1 and not e.g. += 2? This is not possible in C++, I believe.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.