What Is the Conditional Operator?

The conditional operator ?: chooses between two expressions and evaluates only the one its test selects. Three operands feed into that choice, more than any other C++ operator asks for, which is why you will hear it called the ternary operator. Some older material labels it the arithmetic if operator.

Its spelling is split into two symbols, with an operand sitting in each gap:

test ? resultWhenTrue : resultWhenFalse
Operand Where it sits What it contributes
First ahead of the ? gets converted to bool and tested
Second between ? and : becomes the value of the whole expression when the test succeeds
Third after the : becomes the value of the whole expression when the test fails

All three slots have to be filled. An if statement is allowed to stop after its true branch, but a conditional expression has to hand back a value whichever way the test goes, so the : and the operand after it are never optional.

Picking Apart an Evaluation

Here are two lap times, and a program that reports which one was quicker:

#include <iostream>

int main()
{
    const int lapOne{94};
    const int lapTwo{88};

    const int best{(lapOne < lapTwo) ? lapOne : lapTwo};
    const int worst{(lapOne < lapTwo) ? lapTwo : lapOne};

    std::cout << "Fastest lap: " << best << " seconds\n";
    std::cout << "Slowest lap: " << worst << " seconds\n";

    return 0;
}
Fastest lap: 88 seconds
Slowest lap: 94 seconds

Follow the initializer for best. The test reduces to 94 < 88, which is false, so the operator discards its second operand and hands back the third, lapTwo. The initializer for worst runs the identical test but swaps the last two operands, so the same false result now selects lapOne. Those two lines make the roles hard to mix up: whichever operand you put immediately after the ? is the answer for a true test.

Only One Result Operand Is Evaluated

The losing operand is not merely ignored, it is never evaluated in the first place. That matters as soon as either slot contains a function call:

#include <iostream>

int reading(int amount, char sensor)
{
    std::cout << "sensor " << sensor << " was read\n";

    return amount;
}

int main()
{
    constexpr bool usePrimary{true};
    const int depth{usePrimary ? reading(7, 'A') : reading(9, 'B')};

    std::cout << "depth is " << depth << '\n';

    return 0;
}
sensor A was read
depth is 7

Sensor B never announces itself, because usePrimary was true and the third operand was skipped entirely. Anything with a side effect parked in the losing slot simply does not happen.

Both Result Operands Must Reduce to a Single Type

A conditional expression carries one type, and the compiler has to work that type out from the second and third operands. So one of two things must hold: the two operands already agree on a type, or the compiler can convert one of them (or both) until they do. Its conversion rules are intricate, and the type it lands on is not always the one you had in mind.

#include <iostream>

int main()
{
    std::cout << (true ? 10 : 20) << '\n';

    std::cout << (false ? 10 : 2.5) << '\n';

    std::cout << (true ? -10 : 20u) << '\n';

    return 0;
}
10
2.5
4294967286

The first line has two int operands, so nothing needs converting and the true test selects 10. On the second line the operands are int and double, and double is the type both can reach, so the false test selects 2.5. The third line is the one to watch. Its operands are a signed int and an unsigned int, and the common type the compiler settles on is the unsigned one. Converting -10 to unsigned int produces a value near the top of that type's range, which is where 4294967286 comes from on a machine with four-byte integers.

Fundamental types generally mix without drama. Signedness is the exception, and anything that is not a fundamental type is worth converting yourself so the result is not left to the conversion rules.

Warning
A signed value paired with an unsigned value in the second and third operands will be converted to unsigned, and a negative number has no unsigned representation. Keep both operands on the same side of the signed and unsigned divide.
Related Content
The machinery behind that surprising third line is the set of arithmetic conversions, which gets a lesson of its own once we return to type conversion in detail.

When no shared type exists at all, the compiler stops rather than guessing. The next program does not compile:

#include <iostream>

int main()
{
    const int backlog{0};

    std::cout << ((backlog > 0) ? backlog : "queue is empty") << '\n';

    return 0;
}
s.cpp: In function 'int main()':
s.cpp:7:33: error: operands to '?:' have different types 'int' and 'const char*'

An int and a C-style string literal have nothing in common to convert to. The fix is either to give both operands the same type or to stop asking one expression to produce two different shapes of output:

#include <iostream>
#include <string_view>

int main()
{
    const int backlog{4};

    const std::string_view status{(backlog > 0) ? "items waiting" : "queue is empty"};
    std::cout << status << '\n';

    if (backlog > 0)
        std::cout << backlog << " items waiting\n";
    else
        std::cout << "queue is empty\n";

    return 0;
}
items waiting
4 items waiting

The Same Decision, Written Two Ways

Where a decision boils down to selecting one of two values, both forms do the job:

#include <iostream>

int main()
{
    const int members{5};

    int feeFromIfElse{};
    if (members >= 4)
        feeFromIfElse = 18;
    else
        feeFromIfElse = 25;

    const int feeFromConditional{(members >= 4) ? 18 : 25};

    std::cout << "if-else produced " << feeFromIfElse << '\n';
    std::cout << "conditional produced " << feeFromConditional << '\n';

    return 0;
}
if-else produced 18
conditional produced 18

Five lines collapse into one, and the conditional version can be const because it is initialized rather than assigned. The two constructs are not interchangeable in general, though:

if-else ?:
Kind of thing it is a statement an expression
Produces a value no yes
Each branch can hold many statements yes no
Second branch is optional yes no
Usable inside a larger expression no yes
Related Content
Need a refresher on the statement form? Revisit the earlier lesson introducing if statements.

Reaching Places a Statement Cannot Reach

The bottom two rows of that table are the real reason ?: exists. Because it is an expression, it can appear anywhere an expression is allowed, and when its operands are themselves constant expressions the whole thing can be evaluated at compile time:

#include <iostream>

int main()
{
    constexpr bool nightShift{true};
    constexpr int staffOnDuty{nightShift ? 4 : 12};

    std::cout << "Staff on duty: " << staffOnDuty << '\n';

    return 0;
}
Staff on duty: 4

There is no way to drop an if-else into that initializer. Moving the branching outside the declaration will not compile either:

#include <iostream>

int main()
{
    constexpr bool nightShift{true};

    if (nightShift)
        constexpr int staffOnDuty{4};
    else
        constexpr int staffOnDuty{12};

    std::cout << "Staff on duty: " << staffOnDuty << '\n';

    return 0;
}
s.cpp:12:39: error: 'staffOnDuty' was not declared in this scope

Each branch declares its own staffOnDuty, and each one is gone the moment its branch finishes. By the time the print statement runs there is no such name left anywhere in scope. Getting the same result out of an if-else means wrapping the branching in something that does produce a value, namely a function:

#include <iostream>

int staffFor(bool nightShift)
{
    if (nightShift)
        return 4;
    else
        return 12;
}

int main()
{
    const int staffOnDuty{staffFor(true)};

    std::cout << "Staff on duty: " << staffOnDuty << '\n';

    return 0;
}
Staff on duty: 4

That compiles because staffFor(true) is an expression, with the statements safely tucked inside a function body. It also costs an extra function to say what one conditional expression already said.

Precedence Puts ?: Almost Last

Almost every other operator binds tighter than ?:, so a conditional expression standing next to other operators tends to grab more than you meant it to. This program is wrong, and the compiler has nothing to complain about:

#include <iostream>

int main()
{
    const int base{40};
    const int bonus{5};
    const bool overtime{true};

    const int pay{base + overtime ? bonus : 0};

    std::cout << pay << '\n';

    return 0;
}
5

The intended reading was base + (overtime ? bonus : 0), which is 45. What actually happens is that + binds first, so the test becomes base + overtime, or 41. A non-zero int converts to true, the second operand wins, and pay ends up holding bonus alone.

Related Content
The full ordering of operators is set out in the lesson on operator precedence and associativity.

The next statement is wrong in the same way, though this time the compiler does say something:

#include <iostream>

int main()
{
    const int celsius{30};

    std::cout << (celsius > 25) ? "warm" : "cool";
    std::cout << '\n';

    return 0;
}
s.cpp: In function 'int main()':
s.cpp:7:50: warning: second operand of conditional expression has no effect [-Wunused-value]
s.cpp:7:50: warning: third operand of conditional expression has no effect [-Wunused-value]
1

Neither warm nor cool reaches the screen. A bare 1 does.

Note
Tracing it through: celsius > 25 yields true, leaving std::cout << true ? "warm" : "cool". Since operator<< outranks operator?:, the grouping is (std::cout << true) ? "warm" : "cool". Printing true emits 1 and yields std::cout back, so the test is now std::cout itself. A stream converts to bool to report whether it is still in a good state, and after a successful write it is, so the test is true and "warm" survives. What survives is a lone string literal with nowhere to go, which is exactly what the two warnings are pointing at.

Parentheses are the cure, and there are two places worth putting them. The whole conditional operation goes inside one pair whenever it shares a statement with any other operator. The test gets its own pair when it is built out of operators, purely so a reader can see where it ends; a plain function call needs no help. The second and third operands are fine bare.

return isDocked ? 0 : fuelLeft;                      // by itself; test is one bare name
int faster{(lapOne < lapTwo) ? lapOne : lapTwo};     // by itself; test compares two values
std::cout << (isRaining() ? "wet" : "dry");          // sits beside <<; test is a bare invocation
std::cout << ((celsius > 25) ? celsius : 25);        // sits beside <<; test compares two values
Best Practice
Sharing a statement with any other operator is the signal to bracket a conditional operation: one pair of parentheses around all three of its parts. A second pair around the test pays for itself whenever the test is built from operators, since that is what shows a reader where the question ends and the answers begin. A lone function call as the test needs no bracket of its own.

Where the Conditional Operator Earns Its Place

The pattern to look for is a single value arriving from either of two places:

Situation Sketch
Initializing an object const int fee{isMember ? 18 : 25};
Assigning to an object that already exists fee = isMember ? 18 : 25;
Building an argument on its way into a call chargeCard(isMember ? 18 : 25);
Returning from a function return isMember ? 18 : 25;
Printing std::cout << (isMember ? 18 : 25);

Outside that pattern the compactness turns against you. Nested conditionals, conditionals mixed with arithmetic, and conditionals whose operands are themselves long expressions are all harder to read than the if-else they replaced, and harder to get right.

Best Practice
Once an expression is complicated enough that you have to count parentheses to read it, the conditional operator has stopped paying for itself. Write that decision as an if-else instead.

Key Terminology

  • Conditional operator (?:): an operator that tests its first operand and evaluates exactly one of the two operands that follow, producing that operand's value.
  • Ternary operator: an operator taking three operands. ?: is the only one C++ has ever had, so the two names get used interchangeably.
  • Compound expression: an expression containing more than one operator. These are where the conditional operator most needs parentheses.

Looking Forward

The next chapter opens up branching properly: if statements with blocks, the switch statement for testing one value against many, and loops. When you meet constexpr if, you will see another way of resolving a choice before the program ever runs.

Summary

  • ?: is the one operator in C++ that consumes three operands, hence the alternative name ternary operator.
  • The first operand is converted to bool and tested; the second supplies the result on true, the third on false.
  • Only the selected operand is evaluated, so side effects in the other one never happen.
  • All three operands are required. Unlike if, there is no way to omit the false branch.
  • The second and third operands must share a type or be convertible to one, otherwise the code will not compile.
  • Mixing a signed and an unsigned operand converts the signed one to unsigned, which turns negative values into very large ones.
  • ?: is an expression, so it can initialize a const or constexpr object directly; an if-else statement cannot.
  • Nearly all other operators bind more tightly than ?:, so a conditional operation sharing a statement with other operators needs to be parenthesized as a whole.
  • std::cout << cond ? a : b prints 1 or 0 rather than a or b, because operator<< binds first.
  • Use it for picking between two values when initializing, assigning, passing, returning, or printing. Fall back to if-else once the expression grows complicated.