Let’s just get this out of the way. There is no such thing as the
--> operator in C or C++.
What the hell am I talking about, then, when I talk about a
Take a look at this code.
1 2 3 4 5 6 7 8 9
Interestingly enough, this compiles, runs, and even does exactly what it says on the tin; it iterates over an integral value sentinel until it bottoms out at the indicated stopping point.
This block of code, and the idea for this post, comes from an old but famous StackOverflow question that mistakenly refers to this as some form of range operator as well. The truth is much more subtle, and it can provide some lessons for anyone who is new to programming in C or C++.
I rarely write code in the C family of languages anymore, except when I dabble in low-level API’s in Apple’s frameworks, or when I have to write JNI code to interface with low-level hardware at the robotics lab1, but for the first 7 or 8 years that I dabbled in programming, I wrote almost all of my code exclusively in C. I think that it’s an important language for programmers to know; it’s close enough to the metal that it gives you a solid understanding of concepts like memory management, bounds checking, buffers, type safety, etc. Most modern languages abstract a lot of those problems away, but knowing how they do it can make you much better at your job. C gives you a very intuitive notion as to how these things work.
So what’s going on here? For those of you who didn’t decide to jump ahead and read the SO post (you’re a cheater if you did!), this is actually a combination of two operators; the post-decrement operator and the boolean greater than comparison operator. In other words, the above block is equivalent to:
1 2 3 4 5 6 7 8 9
Makes a little more sense when you write it that way, doesn’t it? This is a great way of wrapping your head around pre- and post- increment/decrement operators, too, a topic that new programmers frequently struggle with. Many (bad) programming teachers will frequently tell intro classes that there is no difference between
++x, at least “as far as they’re concerned”. Unfortunately, there’s a huge difference.
++x are the pre- operators. Most programmers worth their salt[^2] know that using a pre-increment or pre-decrement means that the variable is modified before being returned. On the other side of the coin are
x--, the post- operators. With post-incrementation and post-decrementation, the current value of the variable is returned before being fiddled with. Subtle, but important.
int array, a common way to reduce over an array is to index in to the appropriate offset using the loop sentinel and an incrementer. This is frequently done with
for loops, but maybe the logic requires more fine grained control over when the incrementing gets carried out, so you use a while loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
This might look fine off the cuff if you’re not used to seeing code like this, but the grizzled vets at home are shouting at their monitors. This is an off-by-one error, because of the use of pre-increment. You’ll never index in to the 0th element of the array like this. You should have used the post increment operator. Naughty programmer.
Do you see it now?
The mechanics behind this are interesting, too, and often lead to a lot of bad information. It’s very common, especially inside of
for loops, to see old-school systems guys and their mentees explicitly invoke the pre-increment operator, at which point many of us young bucks will call them out for the clumsiness and poor readability of their code. If they’re extra surly and haven’t had enough coffee/Mountain Dew they might throw a copy of K&R at you then publicly shame you, espousing some sort of garbage about performance optimization and loop unrolling.
The truth is that, years ago, gcc used to generate sub-optimal assembly for post-increments that weren’t stored in variables; this had to do with temporary registers. But today, gcc is smart enough to produce the same binaries for uncaptured sentinel values whether they are pre- or post- incremented. So this argument has long become largely irrelevant.
Armed with this new knowledge, we can go back to our original curiosity; the
--> “operator”. It simply takes the value of
x, returns its current value and compares it against (in our specific case) the number 0, and then decrements it in place. Next go around, it is one less. That’s all that’s happening. But it lets you show off to your friends that you know a cool C trick.
C people love this stuff. They love writing weird, crazy code. A part of this is because they can. The language is so incredibly flexible in the sense that it assumes the programmer knows what they’re doing 90% of the time that it’ll let nearly anything fly. It’s not to say that this trick is “bad”, but it’s horribly unreadable; it pretends to be an “operator” that is completely non-standard, it looks suspiciously similar to the structure dereference operator that has nothing to do with integer ranges, and if anybody new to the language saw it they’d get completely baffled by what it was doing and would have a doozy of a time figuring it out by looking through the documentation.
These are the sorts of things we need to work against as programmers. Obfuscated code, especially when done in the name of “performance”, is a blight. Our machines have become spacious and powerful enough in the modern era that this sort of thing is almost always unnecessary.
We have a saying at the lab, which is apparently quite controversial in a lot of circles, but which is one that I hold dear to me; code readability is more important than code correctness. Code that works but is unreadable or complex is a nightmare to change, maintain, debug, and it ramps the bus factor way up. Code that is wrong but readable can be fixed much easier and quicker than it would take to transform code that is right but ugly in to something nice.
Enjoy your fun fact of the day. But for the sake of my sanity, please don’t use it in anything going in to production. Please.
Even then, I’m usually just fiddling with C++ code somebody else wrote. I try to avoid C like the plague these days, as I rarely use it and unless it’s necessary to use I find it to be a wholly unproductive language. I don’t dislike C, necessarily. It just isn’t the right tool for the job when it comes to most of what I do.↩