Wait, You Can Unpack That with Structured Bindings??
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;
} 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;
} 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);
} 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);
} 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;
} Got questions or cool use cases? Find me on LinkedIn!