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.

Structured binding is a C++17 language feature that allows the unpacking of multi-value types—such as arrays, tuples, and aggregate classes—into distinct, named identifiers in a single declaration. It provides a compiler-level mechanism to bind a list of identifiers to the subobjects or elements of an evaluated expression.

Syntax

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 };
  • attr: Optional attributes (e.g., [[maybe_unused]]).
  • cv-auto: The auto keyword, optionally modified by const or volatile.
  • ref-operator: Optional & (lvalue reference) or && (forwarding reference).
  • identifier-list: A comma-separated list of names to bind to the elements.
  • expression: The object being unpacked.

Underlying Mechanics

When a structured binding is declared, the compiler does not directly assign the evaluated expression to the identifiers. Instead, it generates a hidden, anonymous variable (often conceptually referred to as e) initialized by the expression. The cv-auto and ref-operator qualifiers apply to this hidden variable, not directly to the identifiers. The identifiers in the identifier-list are then created as aliases to the specific elements or members of e.
// Given:
const auto& [x, y] = expression;

// The compiler conceptually translates this to:
const auto& e = expression;
// 'x' becomes an alias for the first element of 'e'
// 'y' becomes an alias for the second element of 'e'

Binding Protocols

The compiler resolves the unpacking process by evaluating the type of the hidden variable e against three distinct protocols, evaluated in the following order:

1. Array Binding

If e is an array type, the identifiers bind to the array elements in increasing index order. The number of identifiers in the list must exactly match the number of elements in the array.
int arr[3] = {10, 20, 30};
auto& [a, b, c] = arr; 
// 'e' is an int(&)[3] referring to arr.
// 'a' aliases arr[0], 'b' aliases arr[1], 'c' aliases arr[2].

2. Tuple-Like Binding

If the type of e supports the tuple protocol, the compiler utilizes template metaprogramming hooks to unpack the object. Specifically, the type must have a complete std::tuple_size<E> specialization. The compiler binds each identifier using std::tuple_element<I, E>::type to determine the type, and extracts the value using a get<I>() function. The compiler resolves get<I>() by first looking for a member function e.get<I>(), and if not found, falls back to Argument-Dependent Lookup (ADL) for a non-member get<I>(e).
#include <tuple>

std::tuple<int, double, char> tpl(1, 2.0, 'c');
auto&& [i, d, c] = tpl;
// 'e' is an lvalue reference to tpl (std::tuple<int, double, char>&) because tpl is an lvalue.
// 'i' is bound via std::get<0>(e), 'd' via std::get<1>(e), etc.

3. Data Member Binding

If e is neither an array nor tuple-like, it must be a class or struct type. The compiler binds the identifiers to the non-static data members of e in declaration order. For this to be well-formed:
  • All non-static data members must be public.
  • All non-static data members must be declared in the same class definition (either all in the derived class or all in the same unambiguous base class).
  • The class or struct must not contain any anonymous union members.
  • The number of identifiers must exactly match the number of non-static data members.
struct Point {
    int x;
    int y;
};

Point p{100, 200};
const auto [px, py] = p;
// 'e' is a const Point copy of p.
// 'px' aliases e.x, 'py' aliases e.y.

Type Deduction and decltype Behavior

Because the identifiers are aliases rather than standard variables, querying their type via decltype yields the exact declared type of the referenced member or element, modified by the cv-qualifiers of the hidden variable e. However, decltype explicitly does not inherit the reference qualifiers of e. If e is a const value, the bound identifiers will be treated as const types, even if the original struct members were not declared const, because they are aliasing members of a const object. Conversely, if e is a reference type, decltype on the identifier will not reflect that reference. For example, if e is Point& and Point has a member int x, decltype(px) evaluates to int, not int&.
Master C++ with Deep Grasping Methodology!Learn More