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)

See @Compiler Explorer

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 << ")";

See @Compiler Explorer

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);
}

See @Compiler Explorer

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 :))

References:

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.