Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Tuple-like binding, formally defined in C++17 as structured binding, is a language mechanism that unpacks the subobjects of an array, tuple-like object, or aggregate class into distinct, named identifiers within a single declaration. Rather than creating independent variables, the compiler generates a hidden anonymous entity initialized by the right-hand expression, and the declared identifiers serve as aliases to the subobjects of that hidden entity.

Syntax

Structured bindings support assignment, direct-list, and direct initialization:
attr(optional) cv-auto ref-operator(optional) [ identifier-list ] = expression;
attr(optional) cv-auto ref-operator(optional) [ identifier-list ] { expression };
attr(optional) cv-auto ref-operator(optional) [ identifier-list ] ( expression );
  • cv-auto: The auto keyword, optionally modified by const or volatile.
  • ref-operator: Optional & (lvalue reference) or && (rvalue reference).
  • identifier-list: A comma-separated list of names to bind to the subobjects.
  • expression: The object being unpacked.

The Hidden Object Mechanism

When a structured binding is declared, the compiler conceptually transforms it. The cv-qualifiers and ref-operator apply to the hidden object, not directly to the individual identifiers.
// Given:
const auto& [x, y] = expression;

// The compiler conceptually generates:
const auto& __hidden_e = expression;
// 'x' becomes an alias for the first element of __hidden_e
// 'y' becomes an alias for the second element of __hidden_e

Binding Protocols

The compiler determines how to unpack the expression by evaluating three distinct protocols in a strict sequence.

1. Array Binding

If the expression is an array type, the identifiers bind directly to the array elements. The number of identifiers must exactly match the array size.
int arr[3] = {10, 20, 30};
auto& [a, b, c] = arr; 
// __hidden_e is an int(&)[3]
// a, b, and c act as aliases to arr[0], arr[1], and arr[2].
// For array structured bindings, the declared type of the identifier 
// is exactly the cv-qualified type of the array element. The reference-ness 
// of the hidden object does not propagate. decltype(a) yields 'int', not 'int&'.

2. Tuple-Like Protocol

If the expression’s type E satisfies the tuple-like protocol, the compiler uses standard library templates and getter functions to extract the values. This is triggered if std::tuple_size<E> is a complete type. To satisfy this protocol, a type must provide:
  1. std::tuple_size<E>::value: A compile-time constant dictating the number of elements.
  2. std::tuple_element<I, E>::type: A type trait defining the type of the I-th element.
  3. get<I>(e): A template function to extract the value, resolved either as a member function e.get<I>() or via Argument-Dependent Lookup (ADL). A robust tuple-like implementation requires const and rvalue overloads of get to support all binding contexts.
Custom Tuple-Like Implementation:
struct CustomData {
    int id;
    double value;
};

// 1. Specialize tuple_size
template<>
struct std::tuple_size<CustomData> : std::integral_constant<std::size_t, 2> {};

// 2. Specialize tuple_element
template<>
struct std::tuple_element<0, CustomData> { using type = int; };

template<>
struct std::tuple_element<1, CustomData> { using type = double; };

// 3. Provide get<I> via ADL with necessary overloads
template<std::size_t I>
decltype(auto) get(CustomData& d) {
    if constexpr (I == 0) return (d.id);
    else if constexpr (I == 1) return (d.value);
}

template<std::size_t I>
decltype(auto) get(const CustomData& d) {
    if constexpr (I == 0) return (d.id);
    else if constexpr (I == 1) return (d.value);
}

template<std::size_t I>
decltype(auto) get(CustomData&& d) {
    if constexpr (I == 0) return std::move(d.id);
    else if constexpr (I == 1) return std::move(d.value);
}

template<std::size_t I>
decltype(auto) get(const CustomData&& d) {
    if constexpr (I == 0) return std::move(d.id);
    else if constexpr (I == 1) return std::move(d.value);
}

// Binding execution:
CustomData data{42, 3.14};

auto& [i1, v1] = data;
// __hidden_e is an lvalue reference (CustomData&).
// Triggers get(CustomData&).

auto [i2, v2] = data;
// __hidden_e is declared with auto (a value). 
// The expression passed to get is treated as an xvalue.
// Triggers get(CustomData&&).

const auto [i3, v3] = data;
// __hidden_e is declared with const auto (a const value).
// The expression passed to get is treated as a const xvalue.
// Triggers get(const CustomData&&). 
// Note: If get(const CustomData&&) did not exist, this would fall back 
// to get(const CustomData&) because const lvalue references can bind to rvalues.

3. Data Member Binding

If the type is not an array and does not implement the tuple-like protocol, the compiler falls back to binding directly to the public, non-static data members of the class or struct.
  • All non-static data members must be declared in the same class (either the type itself or a single unambiguous base class).
  • The number of identifiers must exactly match the number of non-static data members.
  • The binding occurs in declaration order.
struct Point {
    int x;
    mutable int y;
};

const Point p{1, 2};
const auto [px, py] = p; 
// __hidden_e is a const Point (due to 'const auto')
// px is an alias to __hidden_e.x (decltype(px) is const int)
// py is an alias to __hidden_e.y (decltype(py) is int, due to mutable)

decltype Behavior

Because structured bindings are aliases rather than standard variables, applying decltype to a bound identifier yields the referenced type specified by the tuple-like protocol (std::tuple_element_t) or the exact declared type of the data member, preserving its referenceness and cv-qualifiers relative to the hidden object.
double some_double = 3.14;
std::pair<int, double&> pr = {1, some_double};
const auto [k, v] = pr;

// decltype(k) is const int
// decltype(v) is double& (yields the exact reference type from std::tuple_element_t; 
// top-level const is ignored on reference types)
Master C++ with Deep Grasping Methodology!Learn More