Replacing an Else-if Sequence With a Ternary Operator
One of the comments left on the Reddit thread of How to make if statements more understandable by /u/loup-vaillant, showed a suggestion to represent an else-if logic a different way, by using the ternary operator (?:
) in a certain way. I find that suggestion interesting and I’d like to share it with you today.
And while you’re here, you may also want to check out other articles about if
statements in general:
- How to make if statements more understandable,
- Do understandable if statements run slower?,
- On using guards in C++
Compacting an else-if with the ternary operator
Consider the following code. It displays a 20×20 square representing a geometrical layout of characters, following these rules in this order of priority:
- if x + y >= 30 (lower-right end), display periods
- if 25 <= x + y < 30 (next slice up), display slashes
- if 20 <= x + y < 25 (next next slice up), display o’s
- if x – 3y > 0 (triangle slice starting at the origin), display pipes
- if x – y > 0 (other triangle slice starting at the origin), diplay backslashes
- fill the rest with underscores.
for (int y = 0; y < 20; ++y) { for (int x = 0; x < 20; ++x) { char displayedChar = 0; if (x + y >= 30) { displayedChar = '.'; } else if (x + y >= 25) { displayedChar = '/'; } else if (x + y >= 20) { displayedChar = 'o'; } else if (x - 3*y > 0) { displayedChar = '|'; } else if (x - y > 0) { displayedChar = '\\'; } else { displayedChar = '_'; } std::cout << displayedChar << ' '; } std::cout << '\n'; }
The suggestion consists in replacing this stretching else-if sequence by repeated calls to the ternary operator:
for (int y = 0; y < 20; ++y) { for (int x = 0; x < 20; ++x) { char displayedChar = (x + y >= 30) ? '.' : (x + y >= 25) ? '/' : (x + y >= 20) ? 'o' : (x - 3*y > 0) ? '|' : (x - y > 0) ? '\\' : '_'; std::cout << displayedChar << ' '; } std::cout << '\n'; }
And both pieces of code output this breathtaking result:
_ | | | | | | | | | | | | | | | | | | | _ _ \ \ | | | | | | | | | | | | | | | o _ _ _ \ \ \ \ | | | | | | | | | | | o o _ _ _ _ \ \ \ \ \ \ | | | | | | | o o o _ _ _ _ _ \ \ \ \ \ \ \ \ | | | o o o o _ _ _ _ _ _ \ \ \ \ \ \ \ \ \ o o o o o _ _ _ _ _ _ _ \ \ \ \ \ \ \ o o o o o / _ _ _ _ _ _ _ _ \ \ \ \ \ o o o o o / / _ _ _ _ _ _ _ _ _ \ \ \ o o o o o / / / _ _ _ _ _ _ _ _ _ _ \ o o o o o / / / / _ _ _ _ _ _ _ _ _ _ o o o o o / / / / / _ _ _ _ _ _ _ _ _ o o o o o / / / / / . _ _ _ _ _ _ _ _ o o o o o / / / / / . . _ _ _ _ _ _ _ o o o o o / / / / / . . . _ _ _ _ _ _ o o o o o / / / / / . . . . _ _ _ _ _ o o o o o / / / / / . . . . . _ _ _ _ o o o o o / / / / / . . . . . . _ _ _ o o o o o / / / / / . . . . . . . _ _ o o o o o / / / / / . . . . . . . . _ o o o o o / / / / / . . . . . . . . .
(The original comment was not on this particular example, instead it took the case of the corresponding article).
The code is not that hard to understand so I don’t think it deserves more explanation. Instead you can let your mind wander into the unexplicably captivating turns of our virtual painting. Just for a minute.
Anyway, the point, at least as I understand it, is that the ternary operator, with the right indentation, makes the code looks like the specification written above in English. And it’s a good thing, because if statements should do their best to look like their specifications.
Why don’t we see this pattern more often then?
One limitation to this technique compared to the else-if sequence is that there can’t be more that one statement for each conditional. If you need to do two things, or instantiate an intermediary variable, the whole thing must replaced by a classical else-if sequence. Unless you take the statements out into a separate function.
EDIT: as pointed out by reader jft, there is a way to fit several instructions, by using the comma operator.
Another thing I noticed is that it’s clearer when it has the indentiation right: all question marks of the ternary operator had better be aligned to make the thing pretty overall. So you need to make sure the indentation of the whole structure stays correct each time you modify it.
And maybe another reason we don’t see this often is because… we don’t see it often. For this reason some could consider it “cute” code, as the expression goes. Not cute in the good way, but rather cute like that:
(it’s kinda cute, isn’t it?)
These are the reasons I could see to explain why this is a rarely seen practice.
Things to know about the ternary operator
Anyway, if we’re considering using this in our code, it’s a good time for talking about the specificities of the ternary operator. Beyond the obvious thing of what the ternary operator is actually doing (b ? x : y
evaluates to x
if b
is true
, and y
if b
is false
), I can see three of its aspects that are worth mentioning.
First, watch out for operator precedence: the precedence of the ternary operator is pretty low, lower than +
, -
, &&
and other common operators. This means that a piece of code that doesn’t have the right parentheses may lead to surprising results, as demonstrated in one of the 42 tips on C++ on the blog of the static analyzer PVS-Studio.
Second, the two possible outcomes of the ternary operator don’t have to be of the same exact type. If one is convertible to the other then it’s enough to have a common type, which will be the result of the ternary expression.
Finally, the evaluated results can be of type void
and/or have side effects. So the ternary operator doesn’t have to return anything, although it often does in practice. For instance, the following code:
void print(std::string const& s) { std::cout << s << '\n'; } isTernaryCool ? print("model") : print("toad");
displays model
if isTernaryCool
evaluates to true
and displays toad
otherwise.
It looks like Lisp, right?
One of my goals for this summer was to become familiar with Lisp. I must say it is a fascinating language, and the book the Land of Lisp that I’m using is lots of fun. If you’re not sure yet how to improve your skills this summer, I’ve got 7 ways to get better at C++ this summer for you.
Anyway, this usage of the ternary operator reminds me of the cond
function in Lisp:
(cond ((>= (+ x y) 30) #\.) ((>= (+ x y) 25) #\/) ((>= (+ x y) 20) #\o) ((> (- x (* 3 y)) 0) #\|) ((> (- x y) 0) #\\) (t #\_))
The cond
function takes a collection of scenarios that consists each of a condition associated to an expression. The cond
function successivelly tries out every condition until it finds one that evaluates to true
(or should I rather say, to t
), and evaluates the associated expression. It looks in principle like an else-if sequence to me, but much more concise. And our usage of the ternary operator in C++ really looks like it, even in terms of layout of the code.
The good sign for it is that Lispers seem to very much appreciate using the cond
function.
What’s your take on this? I’d love to know it. Do you find this usage of the ternary operator too “cute” to be reasonable for production code? And a big thank you to /u/loup-vaillant for reacting to the previous post. If you have other remarks or any kind of feedback, voice it!
Related articles:
- How to Make If Statements More Understandable
- Do Understandable If Statements Run Slower?
- On Using Guards In C++
Share this post!