Structured Bindings (C++17)
Decompose objects into individual named variables
The Problem Before C++17
Accessing individual members of pairs, tuples, or structs required either using first/second or std::tie.
// C++11/14: Verbose tuple/pair access
std::map<std::string, int> scores;
auto result = scores.insert({"Alice", 100});
// Using iterator access
auto iterator = result.first;
bool success = result.second;
// Or using std::tie (creates temporary objects)
std::map<std::string, int>::iterator it;
bool inserted;
std::tie(it, inserted) = scores.insert({"Bob", 95});
// Still awkward: what do 'first' and 'second' mean?C++17: Structured Bindings
Structured bindings allow unpacking aggregates directly into named variables.
// C++17: Clean, readable unpacking
std::map<std::string, int> scores;
auto [iterator, success] = scores.insert({"Alice", 100});
// 'iterator' and 'success' are now individual variables
if (success) {
std::cout << "Inserted: " << iterator->first
<< " = " << iterator->second << std::endl;
}
// Much clearer than result.first / result.second!Supported Types
std::pair
std::pair<int, double> p{1, 3.14};
auto [i, d] = p;
// i = 1, d = 3.14std::tuple
std::tuple<int, char, double> t{1, 'a', 3.14};
auto [i, c, d] = t;
// i = 1, c = 'a', d = 3.14Arrays
int arr[3] = {1, 2, 3};
auto [a, b, c] = arr;
// a = 1, b = 2, c = 3Structs (Public members)
struct Point { int x; int y; };
Point p{10, 20};
auto [px, py] = p;
// px = 10, py = 20Reference Bindings
Use & or && modifiers to bind by reference:
std::pair<int, int> p{1, 2};
// By value (copies)
auto [a, b] = p;
a = 10; // p.first unchanged
// By reference (aliases)
auto& [ref_a, ref_b] = p;
ref_a = 10; // p.first is now 10!
// Const reference (read-only view)
const auto& [ca, cb] = p;
// ca = 10; // ERROR: read-only
// Rvalue reference (for move semantics)
auto&& [rref_a, rref_b] = std::move(p);Practical Applications
1. Range-based For with Maps
std::map<std::string, int> scores = {
{"Alice", 100},
{"Bob", 95},
{"Charlie", 87}
};
// C++17: Clean iteration over maps
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
// No more iterator->first / iterator->second!2. Multiple Return Values
// Return multiple values elegantly
std::tuple<int, int, int> divide(int dividend, int divisor) {
return {
dividend / divisor, // quotient
dividend % divisor, // remainder
dividend % divisor == 0 // is_exact
};
}
// Unpack result
auto [quotient, remainder, is_exact] = divide(17, 5);
std::cout << "17 / 5 = " << quotient
<< " remainder " << remainder << std::endl;3. Decomposing Structs
struct Config {
std::string host;
int port;
bool use_ssl;
};
Config load_config() {
return {"localhost", 8080, false};
}
// Direct unpacking
auto [host, port, ssl] = load_config();
std::cout << "Connecting to " << host << ":" << port << std::endl;Structured Bindings with Attributes
Use [[maybe_unused]] when you don't need all members:
// Only care about the iterator, not the success flag
auto [it, [[maybe_unused]] success] = map.insert({"key", value});
// Or use anonymous binding (C++26 proposal, some compilers support)
// auto [it, _] = map.insert(...); // Not standard yetImplementation Details
Structured bindings create hidden variables ("e") that hold the actual object, with the named bindings becoming aliases:
// What the compiler generates:
auto [x, y] = point;
// Roughly equivalent to:
auto __e = point; // Hidden variable
auto& x = __e.x; // Alias to member
auto& y = __e.y; // Alias to member
// This is why reference modifiers apply to the hidden variable
auto& [rx, ry] = point;
// auto& __e = point; // Reference to original
// auto& rx = __e.x;
// auto& ry = __e.y;Best Practices
- ✓Use structured bindings to improve code readability
- ✓Prefer
const auto&for read-only access - ✓Use meaningful names that describe the data, not the type
- ✗Avoid structured bindings with large objects unless using references
- ✗Don't use for private members (not supported)