41

I'm browsing through some code and I found a few ternary operators in it. This code is a library that we use, and it's supposed to be quite fast.

I'm thinking if we're saving anything except for space there.

What's your experience?

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
hummingBird
  • 2,495
  • 3
  • 23
  • 43
  • 5
    If ternary were faster than if-statements (or vice versa) the compilers would definitely convert one to the other. So they should not have different performance characteristics (assuming you make both statements with equal quality). – Lasse Espeholt Nov 16 '10 at 08:25
  • 2
    If anything, it is a µ-optimization. When in doubt: benchmark. – Gordon Nov 16 '10 at 08:25
  • possible duplicate of [Benefits of using the conditional ?: (ternary) operator](http://stackoverflow.com/questions/3312786/benefits-of-using-the-conditional-ternary-operator) – nawfal Apr 23 '13 at 19:54
  • 1
    huh... bumping old question in order to promote your own? you might find yourself banned my friend... – hummingBird Jul 01 '13 at 11:03
  • 1
    nawfal: that link is specifically for C#, so IMHO not a good duplicate – Tony Delroy Nov 26 '13 at 01:16
  • It would be better to ask for a single language, it is likely not possible to answer otherwise. C++ version: http://stackoverflow.com/questions/3565368/ternary-operator-vs-if-else – Ciro Santilli OurBigBook.com May 19 '16 at 09:42

5 Answers5

66

Performance

The ternary operator shouldn't differ in performance from a well-written equivalent if/else statement... they may well resolve to the same representation in the Abstract Syntax Tree, undergo the same optimisations etc..

Things you can only do with ? :

If you're initialising a constant or reference, or working out which value to use inside a member initialisation list, then if/else statements can't be used but ? : can be:

const int x = f() ? 10 : 2;

X::X() : n_(n > 0 ? 2 * n : 0) { }

Factoring for concise code

Keys reasons to use ? : include localisation, and avoiding redundantly repeating other parts of the same statements/function-calls, for example:

if (condition)
    return x;
else
    return y;

...is only preferable to...

return condition ? x : y;

...on readability grounds if dealing with very inexperienced programmers, or some of the terms are complicated enough that the ? : structure gets lost in the noise. In more complex cases like:

fn(condition1 ? t1 : f1, condition2 ? t2 : f2, condition3 ? t3 : f3);

An equivalent if/else:

if (condition1)
    if (condition2)
        if (condition3)
            fn(t1, t2, t3);
        else
            fn(t1, t2, f3);
    else if (condition3)
            fn(t1, f2, t3);
        else
            fn(t1, f2, f3);
else
    if (condition2)
       ...etc...

That's a lot of extra function calls that the compiler may or may not optimise away.

Further, ? allows you to select an object, then use a member thereof:

(f() ? a : b).fn(g() ? c : d).field_name);

The equivalent if/else would be:

if (f())
    if (g())
        x.fn(c.field_name);
    else
        x.fn(d.field_name);
else
    if (g())
        y.fn(c.field_name);
    else
        y.fn(d.field_name);

Can't named temporaries improve the if/else monstrosity above?

If the expressions t1, f1, t2 etc. are too verbose to type repeatedly, creating named temporaries may help, but then:

  • To get performance matching ? : you may need to use std::move, except when the same temporary is passed to two && parameters in the function called: then you must avoid it. That's more complex and error-prone.

  • c ? x : y evaluates c then either but not both of x and y, which makes it safe to say test a pointer isn't nullptr before using it, while providing some fallback value/behaviour. The code only gets the side effects of whichever of x and y is actually selected. With named temporaries, you may need if / else around or ? : inside their initialisation to prevent unwanted code executing, or code executing more often than desired.

Functional difference: unifying result type

Consider:

void is(int) { std::cout << "int\n"; }
void is(double) { std::cout << "double\n"; }

void f(bool expr)
{
    is(expr ? 1 : 2.0);

    if (expr)
        is(1);
    else
        is(2.0);
}

In the conditional operator version above, 1 undergoes a Standard Conversion to double so that the type matched 2.0, meaning the is(double) overload is called even for the true/1 situation. The if/else statement doesn't trigger this conversion: the true/1 branch calls is(int).

You can't use expressions with an overall type of void in a conditional operator either, whereas they're valid in statements under an if/else.

Emphasis: value-selection before/after action needing values

There's a different emphasis:

An if/else statement emphasises the branching first and what's to be done is secondary, while a ternary operator emphasises what's to be done over the selection of the values to do it with.

In different situations, either may better reflect the programmer's "natural" perspective on the code and make it easier to understand, verify and maintain. You may find yourself selecting one over the other based on the order in which you consider these factors when writing the code - if you've launched into "doing something" then find you might use one of a couple (or few) values to do it with, ? : is the least disruptive way to express that and continue your coding "flow".

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • the direct equivalent of that function call with no ternary operators would have to use intermediate variables and separate conditionals. As long as their scope is local to the function, the compiler should be able to optimise them away. Without them you would have to write a page-full of code that would be impossible to read and debug, although modern compilers would probably do quite a good job at optimising it. – thkala Nov 16 '10 at 08:34
  • @thkala: nice elaboration on my over-terse "Creating temporaries is a hassle too, but guarantees similar performance to the ternary operator.". Compilers probably would get rid of at least some of the if/else function tree, but things can get complicated with register allocations, depth limits etc., and it's hard to know if it'd make it all the way. – Tony Delroy Nov 16 '10 at 08:41
  • I beg a pardon here. Although you are right in explaining it. Conditional operator can be used in an expression where as if()...else() might not be used there. I used this trick to solve item 10 in Scott Meyer's More Effective C++ in a different way where I avoided usage of auto_ptr<> . Have a look : http://siddhusingh.blogspot.com – siddhusingh Jun 25 '13 at 06:50
  • @TonyD : I think you misled by the code. Try to reformat the code in your editor. There isn't any memory leakage. What all I am doing is : I am initializing the member variable pointer based on another variable's value and if not I am assigning it NULL, If true, then also I am first assigning it NULL and then using comma operator I am calling new operator. When last value gets assigned to the value if you use comma operator. In case of any exception I am catching it using catch(...) , cleaning any alloted memory and then rethrowing it again from the constructor itself. – siddhusingh Jun 26 '13 at 11:00
  • @TonyD : Please look at the code below. I think you are confused with the approach I have taken. What you mentioned is right but not in the context I have written. The code below should print 10. #include #include using namespace std; int main(int argc, char ** argv, char * arge[]) try { cout << "Hello World!" << endl; string str = "a"; int * p; int * a = (str != "") ? p=0, new int(10) : 0; if (a == 0) { cout << " a = 0 " << endl; } else { cout << "a = " << *a << endl; } return 0; } catch(...) { cerr << "Uncaught exception..." << endl; } – siddhusingh Jun 29 '13 at 18:13
  • 2
    @siddhusingh: sorry - now noticed your initialiser list is embedding these statements so any pointer is passed back to the data member. But, if `new Image()` throws then `theAudioClip` will be `delete`d despite being uninitialised. You could fix this: all raw pointers should be initialised before the first data member (i.e. `theName`) constructor might throw, given you have a `try`/`catch` around the constructor, as in `: theName((theImage = theAudioClip = nullptr, name))`. Nicer with pointer data members first, but still fragile. – Tony Delroy Jul 01 '13 at 05:47
9

The only potential benefit to ternary operators over plain if statements in my view is their ability to be used for initializations, which is particularly useful for const:

E.g.

const int foo = (a > b ? b : a - 10);

Doing this with an if/else block is impossible without using a function cal as well. If you happen to have lots of cases of const things like this you might find there's a small gain from initializing a const properly over assignment with if/else. Measure it! Probably won't even be measurable though. The reason I tend to do this is because by marking it const the compiler knows when I do something later that could/would accidentally change something I thought was fixed.

Effectively what I'm saying is that ternary operator is important for const-correctness, and const correctness is a great habit to be in:

  1. This saves a lot of your time by letting the compiler help you spot mistakes you make
  2. This can potentially let the compiler apply other optimizations
Flexo
  • 87,323
  • 22
  • 191
  • 272
9

Well...

I did a few tests with GCC and this function call:

add(argc, (argc > 1)?(argv[1][0] > 5)?50:10:1, (argc > 2)?(argv[2][0] > 5)?50:10:1, (argc > 3)?(argv[3][0] > 5)?50:10:1);

The resulting assembler code with gcc -O3 had 35 instructions.

The equivalent code with if/else + intermediate variables had 36. With nested if/else using the fact that 3 > 2 > 1, I got 44. I did not even try to expand this into separate function calls.

Now I did not do any performance analysis, nor did I do a quality check of the resulting assembler code, but at something simple like this with no loops e.t.c. I believe shorter is better.

It appears that there is some value to ternary operators after all :-)

That is only if code speed is absolutely crucial, of course. If/else statements are much easier to read when nested than something like (c1)?(c2)?(c3)?(c4)?:1:2:3:4. And having huge expressions as function arguments is not fun.

Also keep in mind that nested ternary expressions make refactoring the code - or debugging by placing a bunch of handy printfs() at a condition - a lot harder.

thkala
  • 84,049
  • 23
  • 157
  • 201
  • 2
    By the way, it was interesting that when I tried to be smarter than the compiler in the 3 > 2 > 1 code, it backfired spectacularly, as I expected. Conclusion: Never try to outsmart the compiler! – thkala Nov 16 '10 at 09:12
5

If you're worried about it from a performance perspective then I'd be very surprised if there was any different between the two.

From a look 'n feel perspective it's mainly down to personal preference. If the condition is short and the true/false parts are short then a ternary operator is fine, but anything longer tends to be better in an if/else statement (in my opinion).

Sean
  • 60,939
  • 11
  • 97
  • 136
4

You assume that there must be a distinction between the two when, in fact, there are a number of languages which forgo the "if-else" statement in favor of an "if-else" expression (in this case, they may not even have the ternary operator, which is no longer needed)

Imagine:

x = if (t) a else b

Anyway, the ternary operator is an expression in some languages (C,C#,C++,Java,etc) which do not have "if-else" expressions and thus it serves a distinct role there.