C++ Control Flow: Structured Bindings, if-init, switch, Ranges & Modern Iteration (C++23)

C++ Control Flow: Structured Bindings, if-init, switch, Ranges & Modern Iteration (C++23)
Table of Contents
- Structured Bindings: Unpacking Data Elegantly
- Structured Bindings with Custom Types
- if with Initializer (C++17)
- switch with Initializer (C++17)
- Scoped Enums (enum class) and Exhaustiveness
- Range-Based For: Deep Dive
- The Spaceship Operator
<=>and Three-Way Comparison - std::variant and std::visit: Type-Safe Unions
- C++23 range-for with zip and enumerate
- Branchless Programming: Eliminating if in Hot Loops
- Frequently Asked Questions
- Key Takeaway
Structured Bindings: Unpacking Data Elegantly
Structured bindings (C++17) decompose composite objects into named variables — eliminating verbose .first/.second access and manual tuple index subscripting:
Performance note: Structured bindings are zero-cost — they're a syntactic decomposition compiled away completely. The generated assembly is identical to manual .first/.second access.
Structured Bindings with Custom Types
Make your own types work with structured bindings by specializing std::tuple_size, std::tuple_element, and providing a get<N> function:
if with Initializer (C++17)
Variable scope leak is a common source of bugs: a variable declared before an if statement exists throughout the entire enclosing scope even though it's only used in the if block. C++17 solves this:
switch with Initializer (C++17)
Scoped Enums (enum class) and Exhaustiveness
C-style enum values leak their names into the enclosing scope and implicitly convert to integers. enum class fixes both:
Range-Based For: Deep Dive
Range-based for loops work with any type that has begin() and end() iterators — including custom containers:
The Spaceship Operator <=> and Three-Way Comparison
Before C++20, providing full ordering for a type required six operators: <, >, <=, >=, ==, !=. The spaceship operator generates all of them from one declaration:
std::variant and std::visit: Type-Safe Unions
std::variant is a type-safe union — holds exactly one of a set of types at runtime. std::visit dispatches based on the held type:
C++23 Range-For Improvements
C++23 adds std::views::enumerate and std::views::zip — the Python-style conveniences missing from C++20:
Frequently Asked Questions
Is range-based for loop always zero-overhead compared to index loops?
Yes — the compiler converts for (auto& x : container) into for (auto __it = begin(container), __end = end(container); __it != __end; ++__it). There is no runtime difference vs a manual iterator loop. Common compilers vectorize both equally.
Can I break out of a range-based for early?
Yes — break works normally. continue skips to the next iteration. For complex iteration patterns, std::ranges::find_if or algorithms are often cleaner than manual break.
When should I use std::variant instead of inheritance?
Use std::variant when: the set of types is closed and known at compile time, you want value semantics (no heap allocation, copyable), and you want exhaustiveness checking at compile time. Use inheritance (polymorphism) when: the type set needs to be open (extended by users), or when virtual dispatch's runtime flexibility outweighs the overhead.
Key Takeaway
Modern C++ control flow features move logic from the "what" to the "why." Structured bindings declare your intent (I want the key and value from this map entry). If-initializers declare lifetime scope (this iterator only matters inside this if). enum class prevents accidental integer conversion. Every feature reduces the gap between what you intend and what the code does.
Read next: C++ Functions: Parameters, References & noexcept →
Part of the C++ Mastery Course — 30 modules from modern C++ basics to expert systems engineering.
