Structured Bindings C++17 — Decomposing Tuples, Pairs, Arrays, and Structs with auto
In this tutorial, you will learn about Structured Bindings C++17. We cover key concepts, practical examples, and best practices to help you master this topic.
C++17 structured bindings decompose Composite types — tuples, pairs, arrays, and structs — into individual named variables with the auto [x, y, z] syntax, eliminating verbose std::get and .first/.second accesses.
What You'll Learn
You will use structured bindings with pairs, tuples, arrays, and custom structs, apply const and reference qualifiers in decomposition, write functions that return multiple values consumed by structured bindings, use structured bindings in range-for loops over maps, and understand the underlying std::tuple_size and std::tuple_element machinery.
Why It Matters
Before structured bindings, extracting elements from a pair or tuple required verbose calls like auto val = std::get<0>(tuple) or pair.first. Structured bindings make multi-return-value functions first-class citizens in C++, reducing boilerplate and improving readability. They are especially valuable when iterating over maps and when using functions that return multiple results.
Learning Path
graph LR
A["53: Perfect Forwarding"] --> B["54: Structured Bindings"]
B --> C["55: if/switch init + if constexpr"]
C --> D["56: Fold Expressions"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
Basic Structured Bindings with Pairs
The most common use case: extracting key-value pairs.
#include <iostream>
#include <map>
#include <string>
#include <utility>
int main() {
std::map<std::string, int> scores = {
{"Alice", 95},
{"Bob", 87},
{"Charlie", 92}
};
// Before C++17: verbose
for (const auto& entry : scores) {
std::cout << entry.first << ": " << entry.second << "\n";
}
// C++17 structured binding: clean
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n";
}
// Insertion with structured binding
auto [it, inserted] = scores.insert({"David", 88});
if (inserted) {
std::cout << "Inserted " << it->first << "\n";
} else {
std::cout << "Already exists\n";
}
}
Structured Bindings with Tuples
Functions returning tuples become much easier to use.
#include <iostream>
#include <tuple>
#include <string>
#include <algorithm>
// Function returning multiple values
std::tuple<int, double, std::string> getStats(const std::vector<int>& v) {
if (v.empty()) return {0, 0.0, "empty"};
int sum = 0;
for (int x : v) sum += x;
double avg = static_cast<double>(sum) / v.size();
auto [minIt, maxIt] = std::minmax_element(v.begin(), v.end());
return {sum, avg, "processed"};
}
int main() {
std::vector<int> data = {10, 20, 30, 40, 50};
// Structured binding with tuple
auto [total, average, status] = getStats(data);
std::cout << "Sum: " << total << "\n";
std::cout << "Avg: " << average << "\n";
std::cout << "Status: " << status << "\n";
// With const and references
const auto& [ctotal, cavg, cstatus] = getStats(data);
// ctotal, cavg, cstatus are const references to the temporary's members
// (warning: temporary lifetime extension applies to the whole tuple)
// Structured bindings with std::tie alternative (C++11)
int tsum;
double tavg;
std::string tstatus;
std::tie(tsum, tavg, tstatus) = getStats(data);
// Same result, but mutable and pre-declared variables needed
}
Structured Bindings with Arrays
#include <iostream>
#include <array>
int main() {
// C-style array
int coords[3] = {10, 20, 30};
auto [x, y, z] = coords;
std::cout << x << ", " << y << ", " << z << "\n"; // 10, 20, 30
// std::array
std::array<double, 4> values = {1.1, 2.2, 3.3, 4.4};
auto& [a, b, c, d] = values; // References: modifications affect values
a = 99.9;
std::cout << values[0] << "\n"; // 99.9
// 2D array (works recursively? no — 2D array decomposes to rows)
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
auto& [row0, row1] = matrix; // row0 and row1 are int(&)[3]
for (int i : row0) std::cout << i << " ";
std::cout << "\n"; // 1 2 3
}
Structured Bindings with Structs
Any struct with public, non-static data members can be decomposed.
#include <iostream>
#include <string>
struct Point {
double x, y;
};
struct Person {
std::string name;
int age;
std::string city;
};
// Bindings work with public base classes
struct Employee : Person {
double salary;
};
int main() {
Point p{3.5, 2.1};
auto [px, py] = p;
std::cout << px << ", " << py << "\n"; // 3.5, 2.1
Person person{"Alice", 30, "New York"};
auto& [name, age, city] = person; // Modifiable references
age = 31;
std::cout << person.age << "\n"; // 31
// Mix: modifiable and const
const auto& [cn, ca, cc] = person; // All const references
// ca = 32; // Error: ca is const
// With base classes: only the derived members are bound
Employee emp{{"Bob", 25, "Boston"}, 75000};
auto& [ename, eage, ecity] = emp; // Error: can't decompose through inheritance
}
Structured Bindings with Custom Types (Tuple-Like)
Custom types can opt into structured bindings by specializing std::tuple_size, std::tuple_element, and providing a get<I>() method.
#include <iostream>
#include <tuple>
#include <string>
class Color {
int r_, g_, b_;
public:
Color(int r, int g, int b) : r_(r), g_(g), b_(b) {}
// Required for structured bindings
template <size_t I>
int get() const {
if constexpr (I == 0) return r_;
else if constexpr (I == 1) return g_;
else return b_;
}
};
// Specialize tuple_size
template <>
struct std::tuple_size<Color> : std::integral_constant<size_t, 3> {};
// Specialize tuple_element
template <size_t I>
struct std::tuple_element<I, Color> {
using type = int;
};
int main() {
Color c{255, 128, 64};
auto [r, g, b] = c;
std::cout << r << ", " << g << ", " << b << "\n"; // 255, 128, 64
}
Ignoring Elements with std::ignore
You cannot ignore individual elements in a structured binding (unlike std::tie). You must bind all.
#include <iostream>
#include <tuple>
#include <map>
int main() {
// Can't skip elements directly
auto [a, b] = std::pair<int, int>{1, 2};
// auto [x, _] = ... // _ is a real variable name, not "ignore"
// Workaround: use [[maybe_unused]]
[[maybe_unused]] auto [it, inserted] = std::make_pair(1, false);
// For tuple: use std::tie if you must ignore
int x;
std::tie(x, std::ignore, std::ignore) = std::make_tuple(1, 2, 3);
// For map iteration, all elements matter, so this is rarely an issue
std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
for ([[maybe_unused]] const auto& [key, val] : m) {
// Use both key and val
}
}
Binding Modifiers: auto, auto&, const auto&, auto&&
#include <iostream>
#include <tuple>
#include <string>
struct Expensive {
std::string data = "large string that should not be copied";
};
int main() {
auto tup = std::make_tuple(Expensive{}, 42);
// By value: copies each member
auto [e, i] = tup; // Copies Expensive and int
// By reference: no copies
auto& [ref_e, ref_i] = tup; // Reference to tuple's elements
// Const reference
const auto& [cref_e, cref_i] = tup; // Const references
// Forwarding reference
auto&& [fwd_e, fwd_i] = std::move(tup); // Rvalue references (xvalue)
// After move: tup's members are in moved-from state
// fwd_e and fwd_i refer to the now-moved-from elements
}
Common Mistakes
Mistake 1: Forgetting that binding creates new names, not aliases
auto [x, y] = getPoint(); // x and y are new variables
auto& [rx, ry] = getPoint(); // rx, ry are references to the temporary's members
// The temporary returned by getPoint() persists as long as rx/ry exist
Mistake 2: Trying to bind private members
class Secret {
int x_;
public:
int y_;
};
// auto [a, b] = Secret{}; // Error: x_ is private
Structured bindings only work with public, non-static data members (or via tuple-like protocol).
Mistake 3: Binding to a temporary without lifetime extension
std::tuple<int, int> makePair();
auto& [a, b] = makePair(); // References to temporary's members
// OK in this case: the temporary tuple lives until a,b go out of scope
But careful with nested temporaries.
Mistake 4: Thinking structured bindings let you reorder or skip members
auto [z, x, y] = getPoint(); // Error: can't skip 'x' member
All members must be bound, in declaration order.
Mistake 5: Using structured bindings with move-only types by value
auto [ptr, val] = std::make_pair(std::make_unique<int>(5), 10);
// Error: unique_ptr is not copyable
auto [ptr2, val2] = std::move(pair);
// OK: moves unique_ptr
Practice Questions
- What is the output?
std::map<int, std::string> m = {{1, "a"}, {2, "b"}};
for (const auto& [k, v] : m) std::cout << k << v << " ";
Answer: 1a 2b — structured bindings extract key and value from each pair.
What types are required for structured bindings with a struct? Answer: All non-static data members must be public (or the type must specialize tuple_size/tuple_element and provide get()).
How do you bind by reference? Answer:
auto& [a, b] = tuple;— the bound names are references to the source's members.Can structured bindings be used with
std::array? Answer: Yes.std::arraysupports structured bindings via the tuple-like protocol.Write code to iterate over a map and modify values.
std::map<int, int> m = {{1, 10}, {2, 20}};
for (auto& [k, v] : m) v *= 2;
FAQ
Mini Project
Create a function that returns multiple statistics about a container using a struct, and consume it with structured bindings:
#include <iostream>
#include <vector>
#include <string>
#include <numeric>
#include <algorithm>
// Your Stats struct and computeStats function
int main() {
std::vector<int> data = {5, 3, 8, 1, 9, 2, 7};
// Use structured bindings to capture results
auto [min, max, sum, avg] = computeStats(data);
std::cout << "Min: " << min << "\n"; // 1
std::cout << "Max: " << max << "\n"; // 9
std::cout << "Sum: " << sum << "\n"; // 35
std::cout << "Avg: " << avg << "\n"; // 5
// Edge case: empty container
std::vector<int> empty;
auto [emin, emax, esum, eavg] = computeStats(empty);
std::cout << "Empty: min=" << emin << "\n"; // 0
}
This project demonstrates how C++ structured bindings make multi-return-value functions practical and clean, similar to how Python handles multiple return values with tuple unpacking.
What's Next
You now decompose composite types with structured bindings. Next, you will learn C++17 init statements for if/switch and if constexpr — features that scope variables tightly and enable compile-time conditional compilation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro