Last Update:
C++ Templates: How to Iterate through std::tuple: C++26 Packs and Expansion Statements

Table of Contents
In part 1 of this mini-series, we looked at the basics of iterating over a std::tuple
using index_sequence
and fold expressions.
In part 2, we simplified things with std::apply
and even created helpers like for_each_tuple
and transform_tuple
.
So far, we used C++ features up to C++20/23… but now, in C++26, we finally get language-level tools that make tuple iteration straightforward and expressive. In this article, we’ll explore two new techniques:
- Structured bindings can introduce a pack - P1061 - turn a tuple into a pack of variables.
- Expansion statements P1306 - the ultimate “compile-time loop” syntax.
Structured binding packs
Structured bindings have been around since C++17, but in C++26 they gained the ability to introduce packs. That means you can bind an arbitrary number of elements without spelling each out.
For example:
auto [head, ...rest] = std::tuple{1, 2, 3, 4};
// head == 1, rest... expands to (2, 3, 4)
This feature gives us a way to expand tuples into parameter packs, and once we have a pack, we can use fold expressions and other familiar template techniques.
Printing a tuple
We can bind all elements of a tuple into a pack and then fold over it:
#include <tuple>
#include <iostream>
#include <utility>
template <typename Tuple>
void print_tuple(const Tuple& t) {
const auto& [...xs] = t;
bool first = true;
std::cout << "(";
((std::cout << (std::exchange(first, false) ? "" : ", ") << xs), ...);
std::cout << ")";
}
int main() {
print_tuple(std::make_tuple(1, 2.2, "hello"));
}
See at Compiler Explorer
This is a direct replacement for our old index_sequence
or std::apply
approaches - now written in plain C++26.
(Of course, you can easily update it to work with std::print
)
Transforming a tuple
We can also apply a transformation and create a new tuple:
template <typename Tuple, typename Fn>
auto transform_tuple(const Tuple& t, Fn&& fn) {
const auto& [...xs] = t;
return std::make_tuple(fn(xs)...);
}
std::tuple tp{1, 2, 3};
auto doubled = transform_tuple(tp, [](auto x){ return x * 2; });
// doubled == (2, 4, 6)
Here the ...xs
pack is expanded inside std::make_tuple
. This is exactly like writing fn(x1), fn(x2), fn(x3)
, but without the boilerplate.
Dot product
And another example, taken from the proposal:
template <class P, class Q>
constexpr auto dot_product(const P& p, const Q& q) {
const auto& [...ps] = p;
const auto& [...qs] = q;
static_assert(sizeof...(ps) == sizeof...(qs), "Mismatched sizes");
return ((ps * qs) + ...);
}
constexpr std::array<int, 3> a{1, 2, 3};
constexpr std::array<int, 3> b{4, 5, 6};
static_assert(dot_product(a, b) == 32);
Here’s the link to Compiler Explorer
With structured binding packs, tuples and arrays become much more convenient to manipulate.
Expansion statements
While structured binding packs let us turn tuples into packs of variables, expansion statements go even further: they let us write a compile-time loop directly in the language.
In short, you can now write:
template for (...) { }
See this:
auto tp = std::make_tuple(10, 20, 3.14, "hello");
std::cout << "(";
bool first = true;
template for (auto e : tp) {
if (!std::exchange(first, false)) std::cout << ", ";
std::cout << e;
}
std::cout << ")";
No index_sequence
, no std::get
, no std::apply
. Just a clean template for
!
Transforming tuples
We can also transform the tuple:
#include <tuple>
#include <print>
#include <utility>
template <class Tuple>
void print_tuple_expand(const Tuple& t) {
bool first = true;
std::print("(");
template for (const auto& e : t) {
if (!std::exchange(first, false)) {
std::print(", ");
}
std::print("{}", e);
}
std::print(")\n");
}
template <class Tuple, class Fn>
void for_each_tuple_expand(Tuple&& t, Fn&& fn) {
template for (auto&& e : std::forward<Tuple>(t)) {
fn(std::forward<decltype(e)>(e));
}
}
int main() {
auto tp = std::make_tuple(10, 20, 3.14);
print_tuple_expand(tp);
for_each_tuple_expand(tp, [](auto& x) {x *= 2;});
print_tuple_expand(tp);
}
Summary
This article closes the mini-series about tuple iteration. It’s great to see that over the years, C++ has evolved in a way that template/meta programming looks almost the same as regular runtime code. In the first installments of this series, I showed you some tricks, like integer sequence, std::apply… but in C++26, we can remove them and take advantage of template for
.
(Of course, it’s still good to know the tricks up to C++23… as C++26 won’t be used in production for a couple more years :))
- See the part one here: C++ Templates: How to Iterate through std::tuple: the Basics - C++ Stories.
- See the second part here: C++ Templates: How to Iterate through std::tuple: std::apply and More - C++ Stories
References:
- Effective Modern C++ by Scott Meyers
- C++ Templates: The Complete Guide (2nd Edition) by David Vandevoorde, Nicolai M. Josuttis, Douglas Gregor
Back to you
- Do you use std::apply in your code, or do you prefer some manual techniques?
- Do you see other benefits of using
template for
?
Share your feedback in the comments below.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: