Structured Bindings
C++17 introduced structured bindings: a concise syntax for unpacking the elements of a pair, tuple, array, or struct into individually named variables in a single declaration. Instead of writing .first/.second or std::get<0>() every time you touch a piece of a compound value, you give each piece a meaningful name up front.
Before and after
Unpacking a pair: old way vs structured bindings
CPP
#include <iostream>
#include <map>
#include <utility>
std::pair<int, std::string> lookupUser() {
return {42, "Ada"};
}
int main() {
// Before C++17: verbose access through .first / .second
auto result = lookupUser();
int id = result.first;
std::string name = result.second;
std::cout << id << ": " << name << "\n";
// C++17 structured bindings: unpack directly into named variables
auto [userId, userName] = lookupUser();
std::cout << userId << ": " << userName << "\n";
return 0;
}Iterating a map
The single most common and best-loved use of structured bindings in everyday modern C++ is iterating a std::map (or std::unordered_map). Before C++17, every element in a map iteration was a std::pair<const Key, Value>, forcing you to write entry.first and entry.second throughout the loop body. Structured bindings let you name the key and value directly.
Old vs new map iteration
CPP
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> inventory = {
{"apples", 12}, {"bananas", 5}, {"cherries", 30}
};
// Before C++17
for (const auto& entry : inventory) {
std::cout << entry.first << ": " << entry.second << "\n";
}
// With structured bindings — much more readable
for (const auto& [item, count] : inventory) {
std::cout << item << ": " << count << "\n";
}
return 0;
}Unpacking a custom struct
Structured bindings also work directly on the public data members of a plain struct, in declaration order, with no extra code needed on the struct itself.
Structured bindings with a custom struct
CPP
#include <iostream>
struct Point3D {
double x;
double y;
double z;
};
Point3D midpoint(const Point3D& a, const Point3D& b) {
return { (a.x + b.x) / 2, (a.y + b.y) / 2, (a.z + b.z) / 2 };
}
int main() {
Point3D a{0.0, 0.0, 0.0};
Point3D b{4.0, 8.0, 12.0};
auto [mx, my, mz] = midpoint(a, b);
std::cout << "Midpoint: (" << mx << ", " << my << ", " << mz << ")\n";
return 0;
}Works with tuples too
Structured bindings unpack std::tuple the same way they unpack pairs and structs: auto [a, b, c] = myTuple; replaces repeated calls to std::get<0>(myTuple), std::get<1>(myTuple), and so on. You can also bind by reference (auto& [a, b] = pair;) to modify the original object's elements in place, instead of copying them.
auto [a, b] = expr; unpacks a pair, tuple, array, or struct into named variables in one declaration.
The most common real-world use is iterating a map: for (const auto& [key, value] : myMap).
It works on plain structs automatically, binding members in declaration order.
Bind by reference with auto& [...] to mutate the original elements rather than copy them.