Skip to content
C++
C++17Intermediate

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=7

Range-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 9

Binding 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 state

Key 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.

Sign in to track progress