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-basedfor.