Ternary Operator in C++ (?:): Complete Guide with Examples

The ternary operator (?:) picks between two values in one expression. Tested examples: nesting, constexpr, auto, performance, and C vs C++ rules.

Ternary operator in C++: a condition forking into two paths with only one branch taken

Some language features are big; the ternary operator is sharp. It is the only operator in C++ that takes three operands, it compresses a four-line if-else into a single expression, and it can do one thing if-else fundamentally cannot: produce a value. Used well, it makes code read like the decision it encodes. Used badly, it produces the most famous unreadable one-liners in programming.

This guide covers the full picture: syntax and how the operator really works, why “it’s an expression” is the key insight, nested ternaries (and where to stop), constexpr usage, type rules with auto, the performance question settled with an actual compiler experiment, and a genuine C-vs-C++ difference most tutorials never mention. Every example compiles clean with g++ -std=c++17 -Wall -Wextra and runs exactly as shown — including the error messages and the assembly comparison, which were captured from real builds. (Writing C rather than C++? The companion guide to the ternary operator in C covers the C side — and the last section here shows where the two languages genuinely differ.)

Table of Contents

What Is the Ternary Operator in C++?

The ternary operator (?:), also called the conditional operator, is a three-operand C++ operator that selects between two values based on a condition: condition ? value_if_true : value_if_false. The condition is evaluated first and converted to bool; if it is true the whole expression takes the value of the second operand, otherwise the third. Only the chosen branch is evaluated.

Anatomy of the Ternary Operator score >= 50 ? “pass” : “fail” 1. condition (evaluated first) 2. value if true 3. value if false if true if false one value comes out std::string grade = score >= 50 ? "pass" : "fail"; The whole thing is an EXPRESSION — it produces a value you can assign, return, or pass along.

Two details in that definition earn their words. Only the chosen branch is evaluated — the other operand never runs, so side effects in the untaken branch don’t happen. And the condition is contextually converted to bool — any expression that can become a bool works, including pointers and class types with a bool conversion (older tutorials, including the previous version of this article, claimed the condition “must be of integer or pointer type” — that’s not the C++ rule).

The Classic Example

#include <iostream>

int main()
{
    int a = 45, b = 33;

    int max = (a > b) ? a : b;      // condition ? if_true : if_false
    std::cout << max << " is the larger number\n";

    // the same decision with if-else takes four lines:
    int max2;
    if (a > b)
        max2 = a;
    else
        max2 = b;
    std::cout << "if-else agrees: " << max2 << '\n';
    return 0;
}

Output:

45 is the larger number
if-else agrees: 45

Same result — but notice what the if-else version needed that the ternary didn’t: a variable declared uninitialized, then assigned later. That difference is the real story.

The Key Insight: It’s an Expression

if-else is a statement — it does things. The ternary is an expression — it is a value. That means it can go anywhere a value goes:

#include <iostream>
#include <string>

std::string describe(int score)
{
    // ternary in a return statement
    return score >= 50 ? "pass" : "fail";
}

int main()
{
    bool bigJob = true;

    // THE killer use: initializing a const -- impossible with if-else
    const int bufferSize = bigJob ? 4096 : 512;

    // ternary inside a function argument
    std::cout << "grade: " << describe(72) << '\n';
    std::cout << "buffer: " << bufferSize << " bytes\n";

    // inline in output -- note the parentheses (see the gotcha below)
    int items = 1;
    std::cout << items << (items == 1 ? " item\n" : " items\n");
    return 0;
}

Output:

grade: pass
buffer: 4096 bytes
1 item

The const int bufferSize line is the one to remember: a const must be initialized at the moment it exists, and an if-else statement cannot do that. The ternary is the tool for conditional initialization — which is why modern C++, with its preference for const everywhere, uses ?: more than older code did, not less.

The one gotcha worth memorizing: ?: has very low precedence. std::cout << items == 1 ? "a" : "b" does not parse the way it reads — the << happens first. When a ternary sits inside a larger expression, wrap it in parentheses, as the code above does. Cheap insurance, always.

Nested Ternaries: The Ladder, and the Limit

Ternaries nest — and there is exactly one formatting that keeps nesting readable, the decision ladder:

#include <iostream>

int main()
{
    int score = 87;

    // nested ternary, formatted as a decision ladder -- readable
    const char* grade = score >= 90 ? "A"
                      : score >= 80 ? "B"
                      : score >= 70 ? "C"
                      : score >= 60 ? "D"
                      :               "F";

    std::cout << "score " << score << " -> grade " << grade << '\n';

    // largest of three: one nesting level -- the sensible limit
    int a = 4, b = 9, c = 6;
    int max = a > b ? (a > c ? a : c) : (b > c ? b : c);
    std::cout << "largest of " << a << ", " << b << ", " << c
              << " is " << max << '\n';
    return 0;
}

Output:

score 87 -> grade B
largest of 4, 9, 6 is 9

The ladder works because it reads top-to-bottom like a table: first true condition wins. It is the idiomatic replacement for an else-if chain that only assigns a value. The max-of-three shows the other legitimate pattern — branches that are themselves ternaries, kept sane with parentheses and one level of nesting. Beyond that, the honest move is if-else or a function: conciseness that needs a comment to decode isn’t conciseness.

constexpr Ternaries: Deciding at Compile Time

Because it is an expression, the ternary works in every constexpr context — making it the standard way to select compile-time constants:

#include <array>
#include <iostream>

constexpr bool debugBuild = false;

// ternary evaluated entirely at COMPILE time
constexpr int logLevel = debugBuild ? 3 : 1;
constexpr int bufSize  = debugBuild ? 256 : 64;

static_assert(logLevel == 1, "release build expected");

int main()
{
    // the compile-time value sizes a real array type
    std::array<char, bufSize> buffer{};
    std::cout << "log level " << logLevel
              << ", buffer holds " << buffer.size() << " bytes\n";
    return 0;
}

Output:

log level 1, buffer holds 64 bytes

Note what happened: the ternary’s result became a template argument (std::array<char, bufSize>) and was checked by a static_assert — both compile-time contexts where an if statement cannot exist at all. The pattern scales up through modern C++ into if constexpr and C++23’s if consteval for statement-level compile-time branching.

Type Rules: What Comes Out When Branches Differ

Both branches feed one result, so they must share (or convert to) a common type — which is what auto deduces:

#include <iostream>

int main()
{
    int whole = 7;
    double precise = 2.5;
    bool useWhole = false;

    // branches have different types: int and double.
    // The result takes their COMMON type -- double -- so auto is double:
    auto result = useWhole ? whole : precise;
    std::cout << "result = " << result
              << " (size " << sizeof(result) << " bytes -> a double)\n";
    return 0;
}

Output:

result = 2.5 (size 8 bytes -> a double)

The int branch would have been converted to double had it been chosen — the result type is fixed at compile time, not per-run. And when no common type exists, the compiler says so plainly (captured from a real build):

error: operands to '?:' have different types 'int' and 'std::string'

That error is a feature: it is the type system telling you the two branches don’t actually answer the same question.

Ternary vs if-else: Readability and the Performance Myth

Ternary ?:if-else
KindExpression — produces a valueStatement — performs actions
Best forChoosing between two valuesChoosing between two actions
const / member initializers / template args✔ Works✘ Impossible
Multiple statements per branch✘ Don’t✔ Natural
Deep nesting✘ Unreadable fast✔ Scales fine
PerformanceIdentical (verified below)Identical

About that last row — the “ternary is faster” claim survives in forums, so we tested it: the same max function written both ways, compiled with g++ -O2, produces byte-for-byte identical assembly. Both versions compile to a single branchless cmovge instruction — neither even contains a jump. The generated code (captured):

        cmpl    %esi, %edi
        movl    %esi, %eax
        cmovge  %edi, %eax
        ret

Choose between ?: and if-else on readability and expression-vs-statement grounds, never on speed — the optimizer erased that distinction decades ago. (New to if-else itself? Our conditional statements guide starts from zero, and the broader fundamentals live in the basics of C++ programming.)

C vs C++: A Real Difference Most Tutorials Miss

The ternary operator looks identical in C and C++ — but it isn’t. In C++, the ternary result can be an lvalue (something assignable); in C it never is. The same line of code proves it in both compilers:

#include <iostream>

int main()
{
    int a = 3, b = 8;

    // In C++ the ternary result can be an LVALUE:
    // this assigns 0 to whichever variable is larger.
    (a > b ? a : b) = 0;

    std::cout << "a = " << a << ", b = " << b << '\n';
    return 0;
}

C++ (g++): compiles and runs —

a = 3, b = 0

The identical logic in C (gcc): refuses to compile —

error: lvalue required as left operand of assignment
    (a > b ? a : b) = 0;

b was larger, so b got the zero — the ternary selected a variable, not a value. It’s a party trick in application code (assigning through a ternary rarely improves clarity), but it is a genuine window into how differently the two languages define the operator — and exactly the kind of question that shows up in C/C++ interviews. Everything else on this page behaves the same in both languages, as the C ternary guide shows from the C side.

Key Takeaways

  • condition ? if_true : if_false — the condition converts to bool, only the chosen branch is evaluated, and the whole thing produces a value.
  • Expression vs statement is the entire decision rule: choosing between two values → ternary; choosing between two actionsif-else.
  • The ternary’s killer use is conditional initializationconst variables, member initializers, template arguments — places if-else cannot reach.
  • Nested ternaries are fine as a formatted ladder (else-if chains that assign) or one parenthesized level; beyond that, refactor.
  • The performance question is settled: ternary and if-else compile to identical (here: branchless) machine code at -O2 — verified by assembly diff.
  • Wrap ternaries in parentheses inside larger expressions (?: has very low precedence), and remember the C++-only lvalue property: (a > b ? a : b) = 0; compiles in C++, errors in C.

Frequently Asked Questions

Conclusion

The ternary operator is a small feature with a precise job description: turn a two-way decision into a value. Every good use of it — const initialization, return statements, the grade ladder — is that job; every notorious abuse of it is someone trying to make it do if-else‘s job in less space. Hold onto the expression-versus-statement distinction and you will never write, or struggle to read, a bad one.

It’s also a fine lens on how C++ thinks: expressions compose, values flow, and the compiler settles types (and, as the assembly showed, performance) before your program ever runs. For more of the language’s building blocks explained at this depth, our C++ tutorials continue from here.

Scroll to Top