Structured Bindings — Destructuring Made Elegant
Structured bindings (C++17) let you decompose an aggregate — a pair, tuple, array, or plain struct — into individual named variables in a single declaration. Instead of accessing p.first and p.second, or calling std::get<0> on a tuple, you write auto [x, y] = ... and get two clean, named variables. This feature dramatically reduces boilerplate when working with structured return values and range-for loops over maps.
Basic syntax
The syntax is auto [a, b, c, ...] = expression;. The number of names must match the number of elements in the aggregate. The auto can be qualified with const and/or & to control whether the names are references or copies.
#include <tuple>
#include <map>
#include <string>
// Decomposing a std::pair:
std::pair<int, std::string> p{42, "answer"};
auto [id, name] = p; // id=42, name="answer" (copies)
auto& [id2, name2] = p; // id2, name2 are references to p.first, p.second
// Decomposing a std::tuple:
auto t = std::make_tuple(1, 3.14, "hello");
auto [i, d, s] = t; // i=1, d=3.14, s="hello"
// Decomposing an array:
int arr[] = {10, 20, 30};
auto [x, y, z] = arr; // x=10, y=20, z=30
// Decomposing a struct with public members:
struct Point { int x, y; };
Point pt{3, 7};
auto [px, py] = pt; // px=3, py=7Range-for over maps — the killer use case
Before C++17, iterating over a std::map required using entry.first and entry.second, which is readable but verbose. Structured bindings transform this into idiomatic, readable code.
std::map<std::string, int> word_count = {
{"hello", 3}, {"world", 5}, {"foo", 1}
};
// Before C++17:
for (const auto& entry : word_count) {
std::cout << entry.first << ": " << entry.second << "\n";
}
// C++17 structured bindings — much cleaner:
for (const auto& [word, count] : word_count) {
std::cout << word << ": " << count << "\n";
}
// Modifying values while iterating:
for (auto& [word, count] : word_count) {
count *= 2; // doubles all counts in-place
}
// Works with unordered_map, multimap, and any range of pair-like elements too.Structured return values
Functions that need to return multiple values often return a std::pair or std::tuple, or a small struct. Structured bindings make consuming these return values natural.
// Returning multiple values via pair:
std::pair<bool, int> parse_int(std::string_view s) {
// ... parse ...
return {true, 42};
}
auto [ok, value] = parse_int("42");
if (ok) std::cout << value;
// std::map::insert returns pair<iterator, bool>:
std::map<std::string, int> m;
auto [it, inserted] = m.insert({"key", 1});
if (inserted) std::cout << "new entry at " << it->first;
// std::minmax returns pair<T, T>:
auto [lo, hi] = std::minmax({3, 1, 4, 1, 5, 9, 2, 6});
std::cout << lo << " to " << hi; // 1 to 9Binding modes: copy, reference, const
The auto keyword in a structured binding behaves like any other auto declaration — it can be modified with & and const.
std::pair<int, std::string> p{1, "hello"};
// Copy — changes to x/y do not affect p:
auto [x, y] = p;
x = 99; // p.first is still 1
// Const reference — efficient, read-only view:
const auto& [cx, cy] = p;
// cx = 99; // compile error — const
// Mutable reference — changes are reflected in p:
auto& [rx, ry] = p;
rx = 99;
std::cout << p.first; // 99
// Move — for temporary aggregates:
auto [mx, my] = std::move(p); // p is in moved-from stateKey rules to remember
Use const auto& for read-only access — avoids copies and does not modify the source
const auto& [k, v] = entry is the right default in range-for loops. It's efficient (no copy) and safe (no accidental mutation).
The number of names must exactly match the number of elements in the aggregate
If the struct has 3 members and you bind 2 names, it's a compile error. There is no partial binding.
Works with plain structs if all members are public — no special support needed
You don't need to add any tuple_element or get specializations. Any struct with only public non-static data members works automatically.
The binding names are aliases into the bound object — the whole object is 'constructed' once
auto [a, b] = p does not split p into two separate objects. It creates a copy of p and then a and b refer to its members.