Top 5 reasons you should love your ternary operator


I feel the need to take up the cudgels on behalf of my old friend, the ternary operator ?:.

As evidenced by this stackoverflow.com discussion, many people seem to believe that choosing the ternary operator over if/else is purely a matter of personal style, and of readability. Well, it’s not.

It’s an expression

The major difference between the ternary operator and if/else is that the former is an expression, whereas the latter is a statement. This means that you can use the ternary operator anywhere the compiler expects an expression. Here’s a handful of situations where if/else is not an option:

  • When initialising subobjects in the initialiser list of a constructor
  • When initialising const objects, including enumerators in enums
  • When initialising references and other types that have no default constructor, or are not assignable

I hasten to add that you can trivially rewrite every program to not use the ternary operator: Obviously, you can replace

   expr-A ? expr-B : expr-C

with a function that takes all lvalues used in expr-A, expr-B, and expr-C as parameters:

  fun( ...lvalues... ) {
    if ( expr-A ) return expr-B; else return expr-C;
  }

But this way, the conditional code definition is far removed from where it’s used (just the problem lambda functions will solve in C++0x for algorithm customisation), which hurts readability.

It can yield a compile-time constant

The other problem with functions is that their return values are not compile-time constants:

int five() { return 5; }
char array[five()]; // error: 'five()' is not a compile-time constant!

True, C++0x will have constexpr functions, but those cannot contain if/else, so you’re back to square one:

constexpr int oneOrTwo( bool one ) {
    if ( one )     // error: constexpr function body must be a single return statement!
        return 1;
    else
        return 2;
}
constexpr int oneOrThree( bool one ) {
    return one ? 1 : 3 ; // OK
}

In other words:

Makes non-trivial C++0x constexpr functions possible in the first place

For const-aficionados like yours truly, constexpr is a heaven-sent. For the first time, we don’t have to choose between a “proper” class’ convenience (including constructors) and a POD‘s efficiency.

Take your normal Rect class as an example:

struct RectC { int x1, y1, x2, y2; };
class Rect03 {
    int m_x1, m_y1, m_x2, m_y2;
public:
    Rect03( int x1, int y1, int x2, int y2 )
          // normalise coordinates:
        : m_x1( x1 < x2 ? x1 : x2 ), m_y1( y1 < y2 ? y1 : y2 ),
          m_x2( x1 < x2 ? x2 : x1 ), m_y2( y1 < y2 ? y2 : y1 ) {}
// ...
};
class Rect0x {
    int m_x1, m_y1, m_x2, m_y2;
public:
    constexpr Rect0x( int x1, int y1, int x2, int y2 )
          // normalise coordinates:
        : m_x1( x1 < x2 ? x1 : x2 ), m_y1( y1 < y2 ? y1 : y2 ),
          m_x2( x1 < x2 ? x2 : x1 ), m_y2( y1 < y2 ? y2 : y1 ) {}
// ...
};

static const RectC r1[] = {
    { 0, 0, 12, 20 }, { 12, 20, 34, 39 },
}; // ok, really compile-time constant
static const Rect03 r2[] = {
    Rect03( 0, 0, 12, 20 ), Rect03( 12, 20, 34, 39 ),
}; // NOT a compile-time constant!
static const Rect0x r3[] = {
    Rect0x( 0, 0, 12, 20 ), Rect0x( 12, 20, 34, 39 ),
}; // ok, compile-time constant!

The memory layout of all the classes being identical, the Rect03 (C++-03-style) suffers a performance penalty because the constructors cannot be run at compile time, so they must be run at runtime. There’s no reason a class with a constructor should suffer such a severe performance hit over one that doesn’t include a constructor, and constexpr fixes this.

But constexpr functions are severely restricted in what they can do. A constexpr constructor, e.g., must have an empty body ({}); other constexpr functions may only contain a single return statement. All expressions in constexpr functions must be constexprs themselves, after argument substitution.

The ternary operator is your only chance of ever evaluating a condition in such a function.

Incidentally, constexpr non-constructor functions are pure functions: they only depend on their arguments and constant global state. They therefore have a lot in common with functional languages, and the ternary operator helps bring even more functional programming style to C++:

Fosters functional-language-style code

You might not agree with me, but I find functional code a lot easier to read in many cases than procedural code. The ternary operator allows you to write code whose structure mimics functional’s. Compare this Erlang code:

fac(0) -> 1;
fac(1) -> 1;
fac(N) -> N * fac(N-1).

with this C++ code:

int fac( int N ) {
    return N == 0   ? 1 :
           N == 1   ? 1 :
           /* else */ N * fac(N-1) ;
}

and this procedural C++ code:

int fac( int N ) {
    if ( N == 0 )
        return 1;
    else if ( N == 1 )
        return 1;
    else
        return N * fac(N-1);
}

Can contain throw expressions

On thing you might not believe possible is this: The ternary operator can contain throw expressions. Either of the two legs of the ternary may contain a throw expression (but not both); the respective other leg then determines the type of the whole ternary expression.

Here’s an example from Kleopatra:

static QString make_top_label_conflict_text( bool sign, bool enc ) {
    return
        sign && enc ? i18n("Kleopatra cannot unambiguously determine matching certificates "
                           "for all recipients/senders of the message.\n"
                           "Please select the correct certificates for each recipient:") :
        sign        ? i18n("Kleopatra cannot unambiguously determine matching certificates "
                           "for the sender of the message.\n"
                           "Please select the correct certificates for the sender:") :
        enc         ? i18n("Kleopatra cannot unambiguously determine matching certificates "
                           "for all recipients of the message.\n"
                           "Please select the correct certificates for each recipient:" ) :
        /* else */    kleo_assert_fail( sign || enc ) ;
}

(where kleo_assert_fail(x) is a macro which expands to throw Kleo::Exception( ..., #x )).

So, to summarise: the ternary operator is C++’s way of producing a conditional expression (in other programming languages, if/else itself is an expression, but in C++, it’s “only” a statement). There are many places in C++ where statements are not allowed, only expressions, and that’s why the ternary operator is not just syntactic sugar, and choosing ?: over if/else is not just a matter of personal style. The ternary operator is to conditionals what C++0x lambda expressions are to functions.

About marcmutz
Marc Mutz is a Senior Software Engineer, Trainer, and Consultant with KDAB.

8 Responses to Top 5 reasons you should love your ternary operator

  1. Inge Wallin says:

    The world would just be a much better place if we didn’t try to reinvent lisp over again (badly) but just use it.🙂

  2. Benoit Jacob says:

    What you say about constexpr is very true, but notice that you don’t have to wait for c++0x to make this point: you can make the same point in C++98 with enum values and integer template parameters.

  3. Kasper Henriksen says:

    Your example of how to rewrite test-expr ? expr-if-true : expr-if-false; using a function doesn’t work correctly if there are sideeffects of evaluating the untaken branch in the ternary expression.
    For example:

    int result = answer_ready ? the_answer : read_from_disk_a_lot();
    /* versus
    int slooow_result = if(answer_ready, the_answer, read_from_disk_a_lot()); // <-- always runs the function! BAD!
    */

    • marcmutz says:

      You are correct, but you misread what I worte:
      Note that I said that the function needs to take all lvalues as arguments. The expressions themselves need to be moved inside the function, of course:

      int result = answer_ready ? the_answer : read_from_disk_a_lot();
      // versus
      int function( bool answer_ready, int the_answer ) // all lvalues appearing in the ternary expression
      {
          if ( answer_ready )
              return the_answer;
          else
              return read_from_disk_a_lot();
      }
      int result = function( answer_ready, the_answer );
      
  4. John says:

    Good tech reasons for using the ternary operator, but my primary objection is how unreadable it can make code. I really like the way you lay yours out to make it clear, sadly however most people just write it on a single line no matter how complex the evaluation. Perhaps we should add this to the kdelibs coding standard?

  5. Keith Rusler says:

    Nice, but I rarely ever use ternary operator unless the statement is very very trivial. I just wish we could use C++0x when doing cross platform -_-;.

  6. Sérgio Martins says:

    Makes code easier to understand.

    For example, while scrolling through code, when I bump into this:

    QString suffix;
    if ( !KGlobal::locale()->use12Clock() ) {
    suffix = “00”;
    } else {
    suffix = “am”;
    if ( cell > 11 ) {
    suffix = “pm”;
    }
    }

    i see some ifs, a function call, and some other statements, and I have to actually read the code, and look at if-else’s body to know what’s going on.

    But when I see:

    const QString suffix = !KGlobal::locale()->use12Clock() ? suffix :
    cell > 11 ? “am” : “pm”;

    my brain immediately knows this is an initialization, and i don’t have to read the gory details of “am”, “pm” suffixes.

    I understand it’s the initialization of the suffix variable, and keep scrolling code.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: