Iterating over a std::tuple has always been one of those tasks where the gap between what you want to write and what C++ actually lets you write is embarrassingly wide. A tuple holds a fixed set of heterogeneous values, you know them all at compile time, and yet getting a loop over them required either std::apply with a generic lambda, a recursive template instantiation, or index sequences you had to conjure from thin air. The workarounds worked, but they were workarounds.
Bartlomiej Filipek wrote the final part of a tuple-iteration series on isocpp.org back in November 2025, covering how C++26 addresses this with two proposals: structured binding packs (P1061) and expansion statements (P1306). It’s a retrospective look now, but the proposals are worth understanding clearly before C++26 ships.
What the Old Approaches Cost You
The C++17 approach using std::apply was serviceable:
std::apply([](auto&&... args) {
(process(args), ...);
}, my_tuple);
Fold expressions helped, but you’re still wrapping everything in a lambda just to get access to the pack. C++20 and C++23 brought incremental improvements, but none of them eliminated the indirection. Every approach required you to step outside the normal control flow to do something the compiler obviously already knew how to do.
What C++26 Adds
Structured binding packs (P1061) let you unpack a tuple directly into a pack of bindings:
auto [...elems] = my_tuple;
That gives you elems as a pack you can expand anywhere a pack expansion is valid. No index sequence, no apply, no lambda wrapper.
Expansion statements (P1306) go further by giving you an actual loop syntax:
template for (auto elem : my_tuple) {
process(elem);
}
This looks like a range-based for loop, and it reads like one, but the compiler unrolls it at compile time across the tuple’s element types. Each iteration can have a different type for elem, which is the key thing a runtime loop cannot do.
Why This Matters
The practical cases where you want to iterate a tuple come up often enough in real code: visiting variant-like types, serializing heterogeneous records, applying transforms to fields in a struct-of-types design. Each of those has been workable with existing facilities, but the solution always looked more complicated than the problem.
There’s also a teaching cost. Explaining std::apply and fold expressions to someone who just wants to loop over a tuple is a significant detour. The new syntax has an obvious reading: it’s a loop, the loop variable takes on each element’s type in turn, the body runs for each.
C++ has been slowly closing the gap between what the type system knows and what the programmer can express directly. Compile-time reflection in C++26 (P2996) is the bigger headline, but packs and expansion statements are part of the same trajectory. The language is getting better at letting you work with compile-time structure without forcing you to encode that structure as template metaprogramming.
If you’ve been reaching for std::apply every time you needed to touch a tuple’s elements, these two features are worth your attention before C++26 finalizes.