Skip to content

Structured Bindings C++17 — Decomposing Tuples, Pairs, Arrays, and Structs with auto

DodaTech Updated 2026-06-28 9 min read

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

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

  1. 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()).

  2. How do you bind by reference? Answer: auto& [a, b] = tuple; — the bound names are references to the source's members.

  3. Can structured bindings be used with std::array? Answer: Yes. std::array supports structured bindings via the tuple-like protocol.

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

What are structured bindings in C++17

Structured bindings decompose tuples, pairs, arrays, and structs into named variables using auto [x, y, z] syntax, eliminating verbose accessor calls.

Can I skip elements in a structured binding

No. All elements must be bound. Use [[maybe_unused]] for unused bindings, or use std::tie with std::ignore if you need to skip elements.

Do structured bindings support custom types

Yes. Custom types can opt in by specializing std::tuple_size, std::tuple_element, and providing a get() member function.

What is the performance cost of structured bindings

Zero. Structured bindings are purely syntactic sugar — the compiler generates the same code as manual std::get or member access calls.

Can structured bindings work with arrays

Yes. Both C-style arrays and std::array support structured bindings. The number of names must match the array size.

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