Back to Blog
C++ C++17 C++26 Tips

Wait, You Can Unpack That with Structured Bindings??

5 min read

Structured bindings let you unpack objects. Most people use them with std::pair, but they work with more types than you’d expect!

Summary

In C++17, we can use structured bindings to unpack structs, arrays, pairs, and tuples. In C++26, structured bindings gain support for parameter packs. This avoids explicitly naming the members and creates a clean way to print all struct members. We can also use custom template specializations to make any class “tuple-like” and bindable.

Structs

Did you know structured bindings can unpack a struct?


#include <iostream>
#include <string>

struct Person {
    std::string name;
    int age;
    double height;
};

int main() {
    Person person{"Alice", 30, 5.7};
    auto [name, age, height] = person;

    // Print all members
    std::cout << "Name: " << name << "\n";
    std::cout << "Age: " << age << "\n";
    std::cout << "Height: " << height << "\n";

    return 0;
}
Godbolt

Arrays

We can unpack arrays in the same way.


#include <iostream>
#include <array>

int main() {
    std::array<int, 4> nums{10, 20, 30, 40};
    auto [a, b, c, d] = nums;
    std::cout << "Array:   " << a << ", " << b << ", " << c << ", " << d << "\n";

    int raw[3] = {100, 200, 300};
    auto [x, y, z] = raw;
    std::cout << "C-array: " << x << ", " << y << ", " << z << "\n";

    return 0;
}
Godbolt

Pack expansions in C++26

C++26 adds support for parameter packs in structured bindings, meaning we no longer have to explicitly name all the members. As long as we’re inside a template, we can do stuff like this:


#include <print>
#include <array>

struct Point2D {
    int x;
    int y;
};

struct Point3D {
    int x;
    int y;
    int z;
};

template <typename T>
void print_as_array(const T& obj) {
    auto [...members] = obj;

    // The array length is deduced at compile-time
    std::array arr{members...};

    std::println("Array: {}", arr);
}

int main() {
    Point2D p1{1, 2};
    Point3D p2{10, 20, 30};
    print_as_array(p1);
    print_as_array(p2);
}
Godbolt

print_as_array creates a new array with the members of T and prints it. Because we’ve used a fold expression, it works with any number of members. But what if the points had a member that wasn’t an int? It turns out we can still explicitly name members if we want:


#include <print>
#include <array>

struct NamedPoint2D {
    std::string name;
    int x;
    int y;
    bool enabled;
};

struct NamedPoint3D {
    std::string name;
    int x;
    int y;
    int z;
    bool enabled;
};

template <typename T>
void print_as_array(const T& obj) {
    // "Name" and "enabled" are the leading and trailing members respectively
    auto [name, ...members, enabled] = obj;

    // The array length is deduced at compile-time
    std::array arr{members...};

    std::println("Name: {}, Array: {}, Enabled: {}", name, arr, enabled);
}

int main() {
    NamedPoint2D p1{"Flatland", 1, 2, true};
    NamedPoint3D p2{"Sphere", 10, 20, 30, false};
    print_as_array(p1);
    print_as_array(p2);
}
Godbolt

Defining our own bindings

Any class can be used with structured bindings if we make it “tuple-like”. That means specializing std::tuple_size, std::tuple_element, and specializing std::get or defining get as a member function. std::pair, std::tuple, and std::complex are examples of tuple-like classes from the standard library.


#include <iostream>
#include <tuple>
#include <utility>
#include <string>

class Point3D {
    double x_, y_, z_;

public:
    Point3D(double x, double y, double z) : x_(x), y_(y), z_(z) {}

    // get<> as member function or friend
    template <std::size_t I>
    auto get() const {
        if constexpr (I == 0) return x_;
        else if constexpr (I == 1) return y_;
        else if constexpr (I == 2) return z_;
    }
};

// Specialize std::tuple_size
template <>
struct std::tuple_size<Point3D> : std::integral_constant<std::size_t, 3> {};

// Specialize std::tuple_element for each index
template <std::size_t I>
struct std::tuple_element<I, Point3D> {
    using type = double;
};

int main() {
    std::pair<std::string, int> person{"Alice", 30};
    auto [name, age] = person;
    std::cout << "Pair:   " << name << ", " << age << "\n";

    std::tuple<char, double, bool> data{'X', 2.718, true};
    auto [ch, val, flag] = data;
    std::cout << "Tuple:  " << ch << ", " << val << ", " << 
        std::boolalpha << flag << "\n";

    Point3D point{1.0, 2.5, 3.7};
    auto [x, y, z] = point;
    std::cout << "Custom: " << x << ", " << y << ", " << z << "\n";

    return 0;
}
Godbolt

Got questions or cool use cases? Find me on LinkedIn!

JJ Marr

JJ Marr

SDE II @ AMD

Share: