Operator | Symbol | Form | Meaning |
---|---|---|---|
Conditional | ?: | c ? x : y | If conditional c is true then evaluate x , otherwise evaluate y |
The conditional operator (?:
) (also sometimes called the arithmetic if operator) is a ternary operator (an operator that takes 3 operands). Because it has historically been C++’s only ternary operator, it’s also sometimes referred to as “the ternary operator”.
The ?:
operator provides a shorthand method for doing a particular type of if-else statement.
Related content
We cover if-else statements in lesson 4.10 -- Introduction to if statements.
To recap, an if-else statement takes the following form:
if (condition) statement1; else statement2;
If condition
evaluates to true
, then statement1
is executed, otherwise statement2
is executed. The else
and statement2
are optional.
The ?:
operator takes the following form:
condition ? expression1 : expression2;
If condition
evaluates to true
, then expression1
is evaluated, otherwise expression2
is evaluated. The :
and expression2
are not optional.
Consider an if-else statement that looks like this:
if (x > y)
max = x;
else
max = y;
This can be rewritten as:
max = ((x > y) ? x : y);
In such cases, the conditional operator can help compact code without losing readability.
An example
Consider the following example:
#include <iostream>
int getValue()
{
std::cout << "Enter a number: ";
int x{};
std::cin >> x;
return x;
}
int main()
{
int x { getValue() };
int y { getValue() };
int max { (x > y) ? x : y };
std::cout << "The max of " << x <<" and " << y << " is " << max << ".\n";
return 0;
}
First, let’s enter 5
and 7
as input (so x
is 5
, and y
is 7
). When max
is initialized, the expression (5 > 7) ? 5 : 7
is evaluated. Since 5 > 7
is false, this yields false ? 5 : 7
, which evaluates to 7
. The program prints:
The max of 5 and 7 is 7.
Now let’s enter 7
and 5
as input (so x
is 7
, and y
is 5
). In this case, we get (7 > 5) ? 7 : 5
, which is true ? 7 : 5
, which evaluates to 7
. The program prints:
The max of 7 and 5 is 7.
The conditional operator evaluates as part of an expression
Since the conditional operator is evaluated as part of an expression, it can be used anywhere an expression is accepted. In cases where the operands of the conditional operator are constant expressions, the conditional operator can be used in a constant expression.
This allows the conditional operator to be used in places where statements cannot be used.
For example, when initializing a variable:
#include <iostream>
int main()
{
constexpr bool inBigClassroom { false };
constexpr int classSize { inBigClassroom ? 30 : 20 };
std::cout << "The class size is: " << classSize << '\n';
return 0;
}
There’s no direct if-else replacement for this.
You might think to try something like this:
#include <iostream>
int main()
{
constexpr bool inBigClassroom { false };
if (inBigClassroom)
constexpr int classSize { 30 };
else
constexpr int classSize { 20 };
std::cout << "The class size is: " << classSize << '\n'; // Compile error: classSize not defined
return 0;
}
However, this won’t compile, and you’ll get an error message that classSize
isn’t defined. Much like how variables defined inside functions die at the end of the function, variables defined inside an if-statement or else-statement die at the end of the if-statement or else-statement. Thus, classSize
has already been destroyed by the time we try to print it.
If you want to use an if-else, you’d have to do something like this:
#include <iostream>
int getClassSize(bool inBigClassroom)
{
if (inBigClassroom)
return 30;
else
return 20;
}
int main()
{
const int classSize { getClassSize(false) };
std::cout << "The class size is: " << classSize << '\n';
return 0;
}
This one works because getClassSize(false)
is an expression, and the if-else logic is inside a function (where we can use statements). But this is a lot of extra code when we could just use the conditional operator instead.
Parenthesizing the conditional operator
Because C++ prioritizes the evaluation of most operators above the evaluation of the conditional operator, it’s quite easy to write expressions using the conditional operator that don’t evaluate as expected.
Related content
We cover the way that C++ prioritizes the evaluation of operators in future lesson 6.1 -- Operator precedence and associativity.
For example:
#include <iostream>
int main()
{
int x { 2 };
int y { 1 };
int z { 10 - x > y ? x : y };
std::cout << z;
return 0;
}
You might expect this to evaluate as 10 - (x > y ? x : y)
(which would evaluate to 8
) but it actually evaluates as (10 - x) > y ? x : y
(which evaluates to 2
).
Here’s another example that exhibits a common mistake:
#include <iostream>
int main()
{
int x { 2 };
std::cout << (x < 0) ? "negative" : "non-negative";
return 0;
}
You might expect this to print non-negative
, but it actually prints 0
.
Optional reading
Here’s what’s happening in the above example. First, x < 0
evaluates to false
. Then because operator<<
has higher precedence than operator?:
, std::cout << false
is evaluated, which prints 0
(and returns std::cout
).
The partially evaluated expression is now std::cout ? "negative" : "non-negative"
. Since std::cout
is all that is remaining in the condition, the compiler will try to convert it to a bool
so the condition can be resolved. Perhaps surprisingly, std::cout
has a defined conversion to bool
, which will most likely return false
. Assuming it returns false
, we now have false ? "negative" : "non-negative"
, which evaluates to "non-negative"
. So our fully evaluated statement is "non-negative";
. An expression statement that is just a literal (in this case, a string literal) has no effect, so we’re done.
To avoid such evaluation prioritization issues, the conditional operator should be parenthesized as follows:
- Parenthesize the entire conditional operation (including operands) when used in a compound expression (an expression with other operators).
- For readability, consider parenthesizing the condition if it contains any operators (other than the function call operator).
The operands of the conditional operator do not need to be parenthesized.
Let’s take a look at some statements containing the conditional operator and how they should be parenthesized:
return isStunned ? 0 : movesLeft; // not used in compound expression, condition contains no operators
int z { (x > y) ? x : y }; // not used in compound expression, condition contains operators
std::cout << (isAfternoon() ? "PM" : "AM"); // used in compound expression, condition contains no operators (function call operator excluded)
std::cout << ((x > y) ? x : y); // used in compound expression, condition contains operators
Best practice
Parenthesize the entire conditional operation (including operands) when used in a compound expression.
For readability, consider parenthesizing the condition if it contains any operators (other than the function call operator).
The type of the expressions must match or be convertible
To comply with C++’s type checking rules, one of the following must be true:
- The type of the second and third operand must match.
- The compiler must be able to find a way to convert one or both of the second and third operands to matching types. The conversion rules the compiler uses are fairly complex and may yield surprising results in some cases.
For advanced readers
Alternatively, one or both of the second and third operands is allowed to be a throw expression. We cover throw
in lesson 27.2 -- Basic exception handling.
For example:
#include <iostream>
int main()
{
std::cout << (true ? 1 : 2) << '\n'; // okay: both operands have matching type int
std::cout << (false ? 1 : 2.2) << '\n'; // okay: int value 1 converted to double
std::cout << (true ? -1 : 2u) << '\n'; // surprising result: -1 converted to unsigned int, result out of range
return 0;
}
Assuming 4 byte integers, the above prints:
1 2.2 4294967295
In general, it’s okay to mix operands with fundamental types (excluding mixing signed and unsigned values). If either operand is not a fundamental type, it’s generally best to explicitly convert one or both operands to a matching type yourself so you know exactly what you’ll get.
Related content
The surprising case above related to mixing signed and unsigned values is due to the arithmetic conversion rules, which we cover in lesson 10.5 -- Arithmetic conversions.
If the compiler can’t find a way to convert the second and third operands to a matching type, a compile error will result:
#include <iostream>
int main()
{
constexpr int x{ 5 };
std::cout << ((x != 5) ? x : "x is 5"); // compile error: compiler can't find common type for constexpr int and C-style string literal
return 0;
}
In the above example, one of the expressions is an integer, and the other is a C-style string literal. The compiler will not be able to find a matching type on its own, so a compile error will result.
In such cases, you can either do an explicit conversion, or use an if-else statement:
#include <iostream>
#include <string>
int main()
{
int x{ 5 }; // intentionally non-constexpr for this example
// We can explicitly convert the types to match
std::cout << ((x != 5) ? std::to_string(x) : std::string{"x is 5"}) << '\n';
// Or use an if-else statement
if (x != 5)
std::cout << x << '\n';
else
std::cout << "x is 5" << '\n';
return 0;
}
For advanced readers
If x
is constexpr, then the condition x != 5
is a constant expression. In such cases, using if constexpr
should be preferred over if
, and your compiler may generate a warning indicating so (which will be promoted to an error if you are treating warnings as errors).
Since we haven’t covered if constexpr
yet (we do so in lesson 8.4 -- Constexpr if statements), x
is non-constexpr in this example to avoid the potential compiler warning.
So when should you use the conditional operator?
The conditional operator is most useful when doing one of the following:
- Initializing an object with one of two values.
- Assigning one of two values to an object.
- Passing one of two values to a function.
- Returning one of two values from a function.
- Printing one of two values.
Complicated expressions should generally avoid use of the conditional operator, as they tend to be error prone and hard to read.
Best practice
Prefer to avoid the conditional operator in complicated expressions.