Skip to content

Control Flow — if/else, switch, Ternary Operator, If constexpr

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Control Flow. We cover key concepts, practical examples, and best practices to help you master this topic.

C++ provides both runtime control flow with if/else and switch statements and compile-time branching with if constexpr, enabling conditional execution at every level of the program.

What You'll Learn

You will master the if/else statement including dangling else and initialization in if (C++17), the switch statement with fall-through and attributes, the ternary conditional operator, compile-time branching with if constexpr, and how to choose the right control flow construct for each scenario.

Why It Matters

Control flow is how programs make decisions. Without it, every program would execute the same sequence of instructions every time. Mastering control flow means writing code that correctly handles every possible state and input. The C++17 if constexpr is particularly powerful because it eliminates dead branches at compile time, reducing binary size and enabling cleaner template code.

Learning Path

graph LR
    A["06: Operators"] --> B["07: Control Flow"]
    B --> C["08: Loops"]
    C --> D["09: Arrays & C-Strings"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff

The if Statement

#include <iostream>

int main() {
    int temperature = 75;
    
    if (temperature > 80) {
        std::cout << "It is hot\n";
    } else if (temperature > 60) {
        std::cout << "It is pleasant\n";
    } else {
        std::cout << "It is cool\n";
    }
    
    // C++17: if with initializer
    if (int threshold = 70; temperature > threshold) {
        std::cout << "Above threshold (" << threshold << ")\n";
    } else {
        std::cout << "At or below threshold (" << threshold << ")\n";
    }
    // threshold is out of scope here
}

The C++17 initializer form if (init; condition) scopes the variable to the if/else block. This is useful for values derived from a computation that you want to limit in scope.

Dangling Else

if (x > 0)
    if (y > 0)
        std::cout << "positive\n";
else
    std::cout << "negative\n";

Due to the "most closely nested" rule, else associates with the inner if, not the outer one. Always use braces to avoid this ambiguity:

if (x > 0) {
    if (y > 0) {
        std::cout << "positive\n";
    }
} else {
    std::cout << "negative\n";
}

The switch Statement

#include <iostream>

int main() {
    int day = 3;
    
    switch (day) {
        case 1:
            std::cout << "Monday\n";
            break;
        case 2:
            std::cout << "Tuesday\n";
            break;
        case 3:
            std::cout << "Wednesday\n";
            break;
        case 4:
            std::cout << "Thursday\n";
            break;
        case 5:
            std::cout << "Friday\n";
            break;
        case 6:
        case 7:
            std::cout << "Weekend\n";
            break;
        default:
            std::cout << "Invalid day\n";
    }
    
    // [[fallthrough]] attribute (C++17)
    int level = 1;
    switch (level) {
        case 1:
            std::cout << "Basic features\n";
            [[fallthrough]];
        case 2:
            std::cout << "Intermediate features\n";
            [[fallthrough]];
        case 3:
            std::cout << "Advanced features\n";
            break;
    }
}

Key rules for switch:

  • The condition must be integral or enumeration type
  • Each case must be a constant expression
  • A default case is optional but recommended
  • Without break, execution falls through to the next case (this is intentional, not a bug)
  • Mark intentional fall-through with [[fallthrough]] attribute

Ternary Conditional Operator

#include <iostream>

int main() {
    int age = 20;
    std::string status = (age >= 18) ? "Adult" : "Minor";
    std::cout << status << "\n";
    
    // Nested ternary — use sparingly
    int score = 85;
    char grade = (score >= 90) ? 'A'
               : (score >= 80) ? 'B'
               : (score >= 70) ? 'C'
               : (score >= 60) ? 'D'
               : 'F';
    std::cout << grade << "\n";
    
    // Ternary with different types — careful!
    int x = 10;
    auto result = (x > 0) ? 42 : 3.14;  // result is double (3.14)
    std::cout << result << "\n";
}

The ternary operator returns an lvalue if both branches return lvalues of the same type. It is useful for inline conditional initialization but can hurt readability when nested.

if constexpr — Compile-Time Branching (C++17)

if constexpr evaluates a constant expression and discards the non-selected branch at compile time:

#include <iostream>
#include <type_traits>

template <typename T>
auto getValue(T t) {
    if constexpr (std::is_pointer_v<T>) {
        return *t;
    } else {
        return t;
    }
}

int main() {
    int x = 42;
    int* p = &x;
    
    std::cout << getValue(x) << "\n";   // 42 (non-pointer branch)
    std::cout << getValue(p) << "\n";   // 42 (pointer branch)
}

Without if constexpr, the compiler would try to compile both branches for each type, causing errors (e.g., *t on an int). With if constexpr, the unselected branch is not instantiated.

if constexpr is most useful in templates. At runtime, it behaves exactly like a regular if if the condition is not a constant expression.

When to Use Each

Construct Use Case
if/else Runtime boolean conditions, complex logic
switch Multiple discrete values of one integral expression
Ternary ?: Simple inline conditional initialization
if constexpr Compile-time branching in templates

Common Mistakes

Mistake 1: Assignment in Condition

if (x = 5) { ... }  // always true, x set to 5

Write if (x == 5) instead. Enable -Wparentheses to catch this.

Mistake 2: Missing break in Switch

Unintentional fall-through is a common bug. Always include break unless you explicitly want fall-through (and document it with [[fallthrough]]).

Mistake 3: Switch on Non-Integral Type

std::string color = "red";
switch (color) { ... }  // Error: string is not integral

Use if/else chains or std::map for string-based dispatch.

Mistake 4: Dangling Else

The dangling else ambiguity leads to incorrect nesting. Always use braces to clarify intent.

Mistake 5: Ternary Returning Different Types

auto x = condition ? 42 : 3.14;  // warning: precision loss

Both branches should have the same type. If they differ, the compiler applies implicit conversions.

Mistake 6: Using if constexpr in Non-Template Context

if constexpr (true) { ... }  // legal but pointless

Use regular if in non-template code. if constexpr shines in templates.

Practice Questions

  1. What is a dangling else? How do you prevent it?
  2. Write a switch statement that handles days of the week with fall-through for weekends.
  3. Convert this if/else chain to a single ternary expression: if (x > 0) a = 1; else a = -1;
  4. Why does if constexpr exist in addition to regular if?
  5. What happens if you omit break in a switch case?

Challenge

Write a template function to_string that uses if constexpr to handle integer types (converting with std::to_string) and pointer types (converting to hex address with std::stringstream). Test it with int, double, and int*.

FAQ

Can I use `else if` without braces?

Yes, but it is risky. else if is actually else { if (...) }. Always use braces for clarity, especially in larger codebases.

What is the difference between `if constexpr` and `if`?

if constexpr requires a constant expression condition. The non-selected branch is not instantiated, which is critical for template code where some expressions would be ill-formed for certain types.

Can I use `switch` with `enum class`?

Yes. Enumeration types are integral and work perfectly with switch. The compiler can even warn about missing cases.

Is `goto` ever acceptable in C++?

Rarely. In some resource-cleanup patterns and deeply nested loops, goto can be cleaner than complex logic. But 99% of the time there is a better construct.

What is the `[[likely]]` and `[[unlikely]]` attribute?

C++20 added branch prediction hints: if (x > 0) [[likely]] { ... }. These help the compiler optimize branch layout. They are hints, not guarantees.

Can I use `if constexpr` inside a non-template function?

Yes, as long as the condition is a constant expression. But if the condition is always the same, there is no benefit over regular if.

Mini Project

Write a grade calculator that reads a numeric score (0-100) and outputs a letter grade using both if/else and switch:

#include <iostream>

int main() {
    int score;
    std::cout << "Enter score (0-100): ";
    std::cin >> score;
    
    if (score < 0 || score > 100) {
        std::cout << "Invalid score\n";
        return 1;
    }
    
    char grade;
    if (score >= 90) grade = 'A';
    else if (score >= 80) grade = 'B';
    else if (score >= 70) grade = 'C';
    else if (score >= 60) grade = 'D';
    else grade = 'F';
    
    std::cout << "Grade: " << grade << "\n";
    
    // Demonstrate switch with fall-through
    std::string feedback;
    switch (grade) {
        case 'A':
            feedback = "Excellent";
            break;
        case 'B':
            feedback = "Good";
            break;
        case 'C':
            feedback = "Fair";
            break;
        case 'D':
            feedback = "Below average";
            break;
        case 'F':
            feedback = "Failing";
            break;
    }
    
    std::cout << "Feedback: " << feedback << "\n";
}

What's Next

Control flow directs the path of execution. The next lesson covers loops: for, while, do-while, range-based for, and loop control with break and continue.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro