Control Flow

This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com.

This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

if/switch with Initializers

C++17 lets if and switch declare a variable scoped to just the statement, keeping helper variables out of the enclosing scope:

#include <map>
#include <string>

std::map<std::string, int> scores = {{"Ada", 100}};

bool lookup(const std::string& name) {
    if (auto it = scores.find(name); it != scores.end()) {
        return it->second > 50;    // "it" is scoped to this if/else, not leaked into the caller
    }
    return false;
}

int classify(int code) {
    switch (int category = code / 100; category) {
        case 2: return 0;    // 2xx
        case 4: return 1;    // 4xx
        case 5: return 2;    // 5xx
        default: return -1;
    }
}

if constexpr and if consteval

if constexpr (C++17) discards the untaken branch entirely at compile time — essential in templates, where the discarded branch need not even be valid for the types actually instantiated:

#include <type_traits>

template <typename T>
auto describe(T value) {
    if constexpr (std::is_integral_v<T>) {
        return value * 2;             // only instantiated for integral T
    } else {
        return value;                 // only instantiated for non-integral T
    }
}

if consteval (C++23) branches on whether the current evaluation is happening at compile time, letting one function have a fast compile-time path and a separate run-time path:

constexpr double approxSqrt(double x) {
    if consteval {
        // a compile-time-friendly algorithm (e.g. a fixed number of Newton iterations)
        double guess = x / 2 + 1;
        for (int i = 0; i < 20; ++i) guess = (guess + x / guess) / 2;
        return guess;
    } else {
        return __builtin_sqrt(x);     // a run-time intrinsic/library call, unusable in a constant expression
    }
}

Loops and Range-Based for

#include <vector>
#include <iostream>

int main() {
    for (int i = 0; i < 3; ++i) { /* classic C-style */ }

    int n = 3;
    while (n > 0) { --n; }

    do {
        n++;
    } while (n < 1);

    std::vector<int> values = {1, 2, 3};
    for (int v : values) {          // by value -- copies each element
        std::cout << v << ' ';
    }
    for (int& v : values) {          // by reference -- can modify in place
        v *= 2;
    }
    for (const auto& v : values) {   // the usual default: no copy, no accidental mutation
        std::cout << v << ' ';
    }
}

Range-for works on any type exposing begin()/end() (member or free-function ADL-found), so custom containers and views (see Ranges and Views) participate for free.

Structured Bindings

C++17 structured bindings unpack a pair/tuple/struct/array into named variables in one declaration:

#include <map>
#include <string>
#include <iostream>

int main() {
    std::map<std::string, int> ages = {{"Ada", 36}};
    for (const auto& [name, age] : ages) {          // unpacks each pair<const string, int>
        std::cout << name << " is " << age << '\n';
    }

    struct Point { int x, y; };
    Point p{1, 2};
    auto [x, y] = p;                                  // unpacks a struct's public members
    std::cout << x << ',' << y << '\n';
}

[[likely]]/[[unlikely]]

These C++20 attributes hint the optimizer about which branch is hot, without changing program behavior:

int classify(int status) {
    if (status == 200) [[likely]] {
        return 0;
    } else [[unlikely]] {
        return 1;
    }
}

Measure before adding these — an incorrect hint can make the optimizer’s code layout worse.

goto

goto still exists, mainly for breaking out of deeply nested loops (C++ has no labeled break) or centralized cleanup in code that predates RAII:

for (int i = 0; i < 10; ++i) {
    for (int j = 0; j < 10; ++j) {
        if (i * j > 50) goto done;
    }
}
done:
;

Prefer extracting a function (and return`ing) or RAII (see Memory Management and Smart Pointers) over `goto in new code — it remains mostly for legacy/interop reasons.

See Also

  • C: Control Flow — the same statements, without if constexpr, structured bindings, or the range-based for.