Structured binding declaration (since C++17)
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
):
|
||
| 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
Aet qu'aucun ref-qualifier n'est présent, définir e comme attr (optionnel) specifiersA 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
:
- Pour la syntaxe d'initialiseur (1) , les éléments sont copy-initialized .
- Pour les syntaxes d'initialiseur (2,3) , les éléments sont direct-initialized .
-
Sinon, définissez
e
comme
attr
(optionnel)
decl-specifier-seq
ref-qualifier
(optionnel)
einitializer ;.
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
|
(jusqu'à C++26) |
|
Étant donné le nombre d'identifiants dans
sb-identifier-list
comme
N
et la taille de liaison structurée de
|
(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
Eest un type tableau, alors les noms sont liés aux éléments du tableau. -
Cas 2 : Si
Eest 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
Eest 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 deE.
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
getdans la portée deEpar 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 où 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 :
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
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) |