Namespaces
Variants

Structured binding declaration (since C++17)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Lie les noms spécifiés à des sous-objets ou éléments de l'initialiseur.

Comme une référence, une liaison structurée est un alias d'un objet existant. Contrairement à une référence, une liaison structurée ne doit pas nécessairement être de type référence.

attr  (optionnel) decl-specifier-seq ref-qualifier  (optionnel) [ sb-identifier-list ] initializer  ;
attr - séquence d'un nombre quelconque d' attributs
decl-specifier-seq - séquence des spécificateurs suivants (suivant les règles de la déclaration simple ):
(depuis C++26)
ref-qualifier - soit & soit &&
sb-identifier-list - liste d'identifiants séparés par des virgules introduits par cette déclaration , chaque identifiant peut être suivi d'une séquence de spécification d'attributs (depuis C++26)
initializer - un initialiseur (voir ci-dessous)


initializer peut être l'un des éléments suivants :

= expression (1)
{ expression } (2)
( expression ) (3)
expression - toute expression (sauf les expressions virgule non parenthésées)


Une déclaration de liaison structurée introduit tous les identifiants dans la sb-identifier-list comme noms dans la portée englobante et les lie aux sous-objets ou éléments de l'objet désigné par expression . Les liaisons ainsi introduites sont appelées structured bindings .

L'un des identifiants dans la sb-identifier-list peut être précédé d'une ellipse. Un tel identifiant introduit un structured binding pack .

L'identifiant doit déclarer une templated entity .

(since C++26)

Une liaison structurée est un identifiant dans la liste-d'identifiants-sb  qui n'est pas précédé par des points de suspension, ou un élément d'un pack de liaisons structurées introduit dans la même liste d'identifiants (depuis C++26) .

Table des matières

Processus de liaison

Une déclaration de liaison structurée introduit d'abord une variable à nom unique (désignée ici par e ) pour contenir la valeur de l'initialiseur, comme suit :

  • Si expression a un type tableau cv1 A et qu'aucun ref-qualifier n'est présent, définir e comme attr  (optionnel) specifiers A e; , où specifiers est une séquence des spécificateurs dans decl-specifier-seq excluant auto .
Alors chaque élément de e est initialisé à partir de l'élément correspondant de expression comme spécifié par la forme de initializer :
  • Sinon, définissez e comme attr  (optionnel) decl-specifier-seq ref-qualifier  (optionnel) e initializer  ; .

Nous utilisons E pour désigner le type de l'expression d'identifiant e (c'est-à-dire que E équivaut à std:: remove_reference_t < decltype ( ( e ) ) > ).

Une taille de liaison structurée de E est le nombre de liaisons structurées qui doivent être introduites par la déclaration de liaison structurée.

Le nombre d'identifiants dans sb-identifier-list doit être égal à la taille de liaison structurée de E .

(jusqu'à C++26)

Étant donné le nombre d'identifiants dans sb-identifier-list comme N et la taille de liaison structurée de E comme S :

  • S'il n'y a pas de pack de liaison structurée, N doit être égal à S .
  • Sinon, le nombre d'éléments non-pack (c'est-à-dire, N - 1 ) doit être inférieur ou égal à S , et le nombre d'éléments du pack de liaison structurée est S - N + 1 (qui peut être zéro).
(depuis C++26)
struct C { int x, y, z; };
template<class T>
void now_i_know_my() 
{
    auto [a, b, c] = C(); // OK : a, b, c font référence à x, y, z respectivement
    auto [d, ...e] = C(); // OK : d fait référence à x ; ...e fait référence à y et z
    auto [...f, g] = C(); // OK : ...f fait référence à x et y ; g fait référence à z
    auto [h, i, j, ...k] = C();    // OK : le pack k est vide
    auto [l, m, n, o, ...p] = C(); // erreur : la taille du binding structuré est trop petite
}

Une déclaration de liaison structurée effectue la liaison de l'une des trois manières possibles, selon E :

  • Cas 1 : Si E est un type tableau, alors les noms sont liés aux éléments du tableau.
  • Cas 2 : Si E est un type classe non-union et que std:: tuple_size < E > est un type complet avec un membre nommé value (indépendamment du type ou de l'accessibilité d'un tel membre), alors le protocole de liaison "tuple-like" est utilisé.
  • Cas 3 : Si E est un type classe non-union mais que std:: tuple_size < E > n'est pas un type complet, alors les noms sont liés aux membres de données accessibles de E .

Chacun des trois cas est décrit plus en détail ci-dessous.

Chaque liaison structurée possède un type référencé , défini dans la description ci-dessous. Ce type est le type retourné par decltype lorsqu'il est appliqué à une liaison structurée non parenthésée.

Cas 1 : liaison d'un tableau

Chaque liaison structurée dans la sb-identifier-list devient le nom d'une lvalue qui fait référence à l'élément correspondant du tableau. La taille de liaison structurée de E est égale au nombre d'éléments du tableau.

Le type référencé pour chaque liaison structurée est le type d'élément du tableau. Notez que si le type de tableau E est qualifié cv, son type d'élément l'est également.

int a[2] = {1, 2};
auto [x, y] = a;    // crée e[2], copie a dans e,
                    // puis x fait référence à e[0], y fait référence à e[1]
auto& [xr, yr] = a; // xr fait référence à a[0], yr fait référence à a[1]

Cas 2 : liaison d'un type implémentant les opérations de tuple

L'expression std:: tuple_size < E > :: value doit être une expression constante entière bien formée, et la taille de la liaison structurée de E est égale à std:: tuple_size < E > :: value .

Pour chaque liaison structurée, une variable dont le type est "référence vers std:: tuple_element < I, E > :: type " est introduite : référence lvalue si son initialisateur correspondant est une lvalue, référence rvalue sinon. L'initialisateur pour la I ième variable est

  • e. get < I > ( ) , si la recherche de l'identifiant get dans la portée de E par recherche d'accès membre de classe trouve au moins une déclaration qui est un modèle de fonction dont le premier paramètre de modèle est un paramètre constant
  • Sinon, get < I > ( e ) , où get est recherché uniquement par recherche dépendante des arguments , en ignorant la recherche non-ADL.

Dans ces expressions d'initialisation, e est une lvalue si le type de l'entité e est une référence lvalue (ceci se produit uniquement si le ref-qualifier est & ou s'il est && et que l'expression d'initialisation est une lvalue) et une xvalue sinon (cela effectue effectivement une sorte de perfect forwarding), I est une prvalue std::size_t , et < I > est toujours interprété comme une liste de paramètres template.

La variable a la même durée de stockage que e .

La liaison structurée devient alors le nom d'un lvalue qui fait référence à l'objet lié à ladite variable.

Le type référencé pour la I ième liaison structurée est std:: tuple_element < I, E > :: type .

float x{};
char  y{};
int   z{};
std::tuple<float&, char&&, int> tpl(x, std::move(y), z);
const auto& [a, b, c] = tpl;
// using Tpl = const std::tuple<float&, char&&, int>;
// a désigne une liaison structurée qui fait référence à x (initialisée à partir de get<0>(tpl))
// decltype(a) est std::tuple_element<0, Tpl>::type, c'est-à-dire float&
// b désigne une liaison structurée qui fait référence à y (initialisée à partir de get<1>(tpl))
// decltype(b) est std::tuple_element<1, Tpl>::type, c'est-à-dire char&&
// c désigne une liaison structurée qui fait référence à la troisième composante de tpl, get<2>(tpl)
// decltype(c) est std::tuple_element<2, Tpl>::type, c'est-à-dire const int

Cas 3 : liaison aux membres de données

Chaque membre de données non statique de E doit être un membre direct de E ou de la même classe de base de E , et doit être bien formé dans le contexte de la liaison structurée lorsqu'il est nommé comme e. name . E ne peut pas avoir de membre union anonyme. La taille de liaison structurée de E est égale au nombre de membres de données non statiques.

Chaque liaison structurée dans sb-identifier-list devient le nom d'une lvalue qui fait référence au membre suivant de e dans l'ordre de déclaration (les champs de bits sont pris en charge) ; le type de la lvalue est celui de e. mI , où mI fait référence au I ième membre.

Le type référencé de la I ième liaison structurée est le type de e. mI s'il n'est pas un type référence, ou le type déclaré de mI dans le cas contraire.

#include <iostream>
struct S
{
    mutable int x1 : 2;
    volatile double y1;
};
S f() { return S{1, 2.3}; }
int main()
{
    const auto [x, y] = f(); // x est une lvalue int identifiant le champ de bits de 2 bits
                             // y est une lvalue const volatile double
    std::cout << x << ' ' << y << '\n';  // 1 2.3
    x = -2;   // OK
//  y = -2.;  // Erreur : y est qualifié const
    std::cout << x << ' ' << y << '\n';  // -2 2.3
}

Ordre d'initialisation

Soit valI l'objet ou la référence désigné(e) par la I ième liaison structurée dans sb-identifier-list :

  • L'initialisation de e est séquencée avant l'initialisation de tout valI .
  • L'initialisation de chaque valI est séquencée avant l'initialisation de tout valJ I est inférieur à J .

Notes

Les liaisons structurées ne peuvent pas être contraintes :

template<class T>
concept C = true;
C auto [x, y] = std::pair{1, 2}; // error: constrained
(depuis C++20)

La recherche du membre get ignore l'accessibilité comme d'habitude et ignore également le type exact du paramètre template constant. Un membre privé template < char * > void get ( ) ; entraînera l'utilisation de l'interprétation de membre, même si elle est mal formée.

La partie de la déclaration précédant [ s'applique à la variable cachée e , et non aux liaisons structurées introduites :

int a = 1, b = 2;
const auto& [x, y] = std::tie(a, b); // x et y sont de type int&
auto [z, w] = std::tie(a, b);        // z et w sont toujours de type int&
assert(&z == &a);                    // réussi

L'interprétation de type tuple est toujours utilisée si std:: tuple_size < E > est un type complet avec un membre nommé value , même si cela devait rendre le programme mal formé :

struct A { int x; };
namespace std
{
    template<>
    struct tuple_size<::A> { void value(); };
}
auto [x] = A{}; // erreur ; l'interprétation "membre de données" n'est pas prise en compte.

Les règles habituelles pour la liaison des références aux temporaires (y compris l'extension de durée de vie) s'appliquent si un ref-qualifier est présent et que l' expression est une prvalue. Dans ces cas, la variable cachée e est une référence qui se lie à la variable temporaire matérialisée à partir de l'expression prvalue, prolongeant sa durée de vie. Comme d'habitude, la liaison échouera si e est une référence lvalue non-const :

int a = 1;
const auto& [x] = std::make_tuple(a); // OK, pas de référence pendante
auto&       [y] = std::make_tuple(a); // erreur, impossible de lier auto& à une rvalue std::tuple
auto&&      [z] = std::make_tuple(a); // également OK

decltype ( x ) , où x désigne une liaison structurée, désigne le type référencé de cette liaison structurée. Dans le cas de type tuple, il s'agit du type retourné par std::tuple_element , qui peut ne pas être une référence même si une référence cachée est toujours introduite dans ce cas. Cela émule effectivement le comportement de la liaison à une structure dont les membres de données non statiques ont les types retournés par std::tuple_element , la référencité de la liaison elle-même n'étant qu'un détail d'implémentation.

std::tuple<int, int&> f();
auto [x, y] = f();       // decltype(x) est int
                         // decltype(y) est int&
const auto [z, w] = f(); // decltype(z) est const int
                         // decltype(w) est int&

Les liaisons structurées ne peuvent pas être capturées par les expressions lambda :

#include <cassert>
int main()
{
    struct S { int p{6}, q{7}; };
    const auto& [b, d] = S{};
    auto l = [b, d] { return b * d; }; // valid since C++20
    assert(l() == 42);
}
(jusqu'en C++20)


La taille d'une liaison structurée peut être 0 tant que la sb-identifier-list contient exactement un identifiant qui ne peut introduire qu'un pack de liaison structurée vide.

auto return_empty() -> std::tuple<>;
template <class>
void test_empty()
{
    auto [] = return_empty(); // erreur
    auto [...args] = return_empty(); // OK, args est un pack vide
    auto [one, ...rest] = return_empty(); // erreur, la taille de la liaison structurée est trop petite
}
(depuis C++26)
Macro de test de fonctionnalité Valeur Std Fonctionnalité
__cpp_structured_bindings 201606L (C++17) Liaisons structurées
202403L (C++26) Liaisons structurées avec attributs
202406L (C++26) Déclaration de liaison structurée comme condition
202411L (C++26) Les liaisons structurées peuvent introduire un pack

Mots-clés

auto

Exemple

#include <iomanip>
#include <iostream>
#include <set>
#include <string>
int main()
{
    std::set<std::string> myset{"hello"};
    for (int i{2}; i; --i)
    {
        if (auto [iter, success] = myset.insert("Hello"); success) 
            std::cout << "L'insertion a réussi. La valeur est "
                      << std::quoted(*iter) << ".\n";
        else
            std::cout << "La valeur " << std::quoted(*iter)
                      << " existe déjà dans l'ensemble.\n";
    }
    struct BitFields
    {
        // C++20 : initialiseur par défaut pour les champs de bits
        int b : 4 {1}, d : 4 {2}, p : 4 {3}, q : 4 {4};
    };
    {
        const auto [b, d, p, q] = BitFields{};
        std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n';
    }
    {
        const auto [b, d, p, q] = []{ return BitFields{4, 3, 2, 1}; }();
        std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n';
    }
    {
        BitFields s;
        auto& [b, d, p, q] = s;
        std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n';
        b = 4, d = 3, p = 2, q = 1;
        std::cout << s.b << ' ' << s.d << ' ' << s.p << ' ' << s.q << '\n';
    }
}

Sortie :

L'insertion a réussi. La valeur est "Hello".
La valeur "Hello" existe déjà dans l'ensemble.
1 2 3 4
4 3 2 1
1 2 3 4
4 3 2 1

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement publié Comportement correct
CWG 2285 C++17 expression pourrait faire référence aux noms de identifier-list la déclaration est
mal formée dans ce cas
CWG 2312 C++17 la signification de mutable était perdue dans le cas 3 sa signification est toujours conservée
CWG 2313 C++17 dans le cas 2, les variables de liaison structurée pouvaient être redéclarées ne peuvent pas être redéclarées
CWG 2339 C++17 dans le cas 2, la définition de I était manquante définition ajoutée
CWG 2341
( P1091R3 )
C++17 les liaisons structurées ne pouvaient pas être
déclarées avec une durée de stockage statique
autorisé
CWG 2386 C++17 le protocole de liaison "tuple-like" était utilisé
chaque fois que std:: tuple_size < E > est un type complet
utilisé seulement lorsque std:: tuple_size < E >
a un membre value
CWG 2506 C++17 si expression est d'un type tableau qualifié cv,
la qualification cv était reportée sur E
rejette cette qualification cv
CWG 2635 C++20 les liaisons structurées pouvaient être contraintes interdit
CWG 2867 C++17 l'ordre d'initialisation n'était pas clair clarifié
P0961R1 C++17 dans le cas 2, le membre get était utilisé
si la recherche trouve un get de tout type
seulement si la recherche trouve un modèle de fonction
avec un paramètre constant
P0969R0 C++17 dans le cas 3, les membres devaient être publics seulement requis d'être accessibles
dans le contexte de la déclaration

Références

  • Norme C++23 (ISO/CEI 14882:2024) :
  • 9.6 Déclarations de liaison structurée [dcl.struct.bind] (p: 228-229)
  • Norme C++20 (ISO/CEI 14882:2020) :
  • 9.6 Déclarations de liaison structurée [dcl.struct.bind] (p: 219-220)
  • Norme C++17 (ISO/IEC 14882:2017) :
  • 11.5 Déclarations de liaison structurée [dcl.struct.bind] (p: 219-220)

Voir aussi

(C++11)
crée un tuple de références de lvalue ou décompose un tuple en objets individuels
(fonction template)