Some love for the conditional operator

The conditional (ternary) operator almost falls in the same category as goto in that it its use is frowned upon and can be easily circumvented. Go To Statement Considered Harmful by Edsger Dijkstra snowballed, despite some good arguments (see Structured Programming with go to Statements by Donald E. Knuth), into banning goto from most workplaces. This fate has not (yet) been shared by the conditional operator, but there are certainly voices out there that would like to see this happen [1] (apologies for the reference).

Admittedly, the conditional operator is something like an acquired taste. In the beginning of my professional career, I too, used to prefer the much more verbose and therefore arguably more readable if – else statements. Reading the conditional operator is like reading the ramblings of three grunting cave men. The first questioning a statement, the second giving the answer should the statement be true and the third mentioning the alternative.

The formatting is heavily inspired by an article of Anders Lindgren, IAR Systems “Thoughts on readable code: Conditional expressions”, which in turn seems to be inspired by reverse polish notation. The article does not seem to be available anymore. I had to dig it up via the wayback machine. It is a short but good read. It may be another acquired taste, but the formatting rules are simple and allow one to read complex expressions and statements that would otherwise be incomprehensible.
ans =  Huh   // cave man 1: Huh?
     ? Duh   // cave man 2: Duh!
     : Doh;  // cave man 3: Doh.

If – else seems Shakespeare next to this. Then again, one could get bored reading Shakespeare. Nothing wrong with a pulp fiction page-turner, especially if it is about grunting cave men (Dan Brown owes me for this idea). There are a small number of situations where I prefer the use of the conditional operator over the alternatives. One such case is conditional initialization of variables.

Compare :

// Get the (4-bit) values for trigger, guard, effect and state from an array
// of bytes. 'idx' is an offset in the array, 'nibIdx' is a relative offset
// to the nibble (4-bit). Type is a 4-bit bitmask indicating what data to
// expect in the byte array. If the data is present the corresponding bit in
// type is set and the value is read. Otherwise the value is INVALID.

uint8_t trigger = INVALID;
uint8_t guard   = INVALID;
uint8_t effect  = INVALID;
uint8_t state   = INVALID;

if ( type & triggerBit ) {
    trigger = GetValue(pFsm, idx, nibIdx++);
}
if ( type & guardBit ) {
    guard = GetValue(pFsm, idx, nibIdx++);
}
if ( type & effectBit ) {
    effect = GetValue(pFsm, idx, nibIdx++);
}
if ( type & stateBit ) {
    state = GetValue(pFsm, idx, nibIdx++);
}

against :

    
// Get the (4-bit) values for trigger, guard, effect and state from an array
// of bytes. 'idx' is an offset in the array, 'nibIdx' is a relative offset
// to the nibble (4-bit). Type is a 4-bit bitmask indicating what data to
// expect in the byte array. If the data is present the corresponding bit in
// type is set and the value is read. Otherwise the value is set to INVALID.

uint8_t trigger, guard, effect, state;

trigger = (type & triggerBit ) ? GetValue(pFsm, idx, nibIdx++) : INVALID;
guard   = (type & guardBit   ) ? GetValue(pFsm, idx, nibIdx++) : INVALID;
effect  = (type & effectBit  ) ? GetValue(pFsm, idx, nibIdx++) : INVALID;
state   = (type & stateBit   ) ? GetValue(pFsm, idx, nibIdx++) : INVALID;

Note that this example contains a number of things that in general could be considered bad practice. The multiple declarations on a single line are here to limit the width of the following lines for online readability. The use of the postfix increment (because of the side effect) inside a function is usually not a good idea. In my opinion the ‘sin’ improves the brevity and readability of this code snippet. This approach is also often used for checking whether a value is null. So often that C#, Perl, and Swift introduce a null coalescing operator. Another case where the conditional operator comes in handy is when calling a function with a large set of parameters of which one parameter is conditional.

Compare :

float angle = 0;
if ( hidden ) {
    angle = M_PI;
}

RotateTransform( &transform,  // a 3D transform
                 1         ,  // x-axis
                 0         ,  // y-axis
                 0         ,  // z-axis
                 angle     ); // rotation angle

or worse :

if ( hidden ) {
    RotateTransform( &transform,  // a 3D transform
                     1         ,  // x-axis
                     0         ,  // y-axis
                     0         ,  // z-axis
                     M_PI      ); // rotation angle 
} else {
    RotateTransform( &transform,  // a 3D transform
                     1         ,  // x-axis
                     0         ,  // y-axis
                     0         ,  // z-axis
                     0         ); // rotation angle 
}

against :

RotateTransform( &transform        ,  // a 3D transform
                 1                 ,  // x-axis
                 0                 ,  // y-axis
                 0                 ,  // z-axis
                 hidden ? M_PI : 0 ); // rotation angle

Especially if the code is part of a larger function the latter approach immediately conveys the similarities and differences between both branches.