Strukturierte Bindungsdeklaration (seit C++17)
Bindet die angegebenen Namen an Unterobjekte oder Elemente des Initialisierers.
Ähnlich wie eine Referenz ist eine strukturierte Bindung ein Alias für ein vorhandenes Objekt. Im Gegensatz zu einer Referenz muss eine strukturierte Bindung keinen Referenztyp haben.
attr (optional) decl-specifier-seq ref-qualifier (optional) [ sb-identifier-list ] initializer ; |
|||||||||
| attr | - | Sequenz einer beliebigen Anzahl von Attributen | ||
| decl-specifier-seq | - | Sequenz der folgenden Spezifizierer (gemäß den Regeln der einfachen Deklaration)
| ||
| ref-qualifier | - | entweder & oder && | ||
| sb-identifier-list | - | Liste von durch Kommas getrennten Bezeichnern, die durch diese Deklaration eingeführt werden, jedem Bezeichner kann eine Attribut-Spezifizierer-Sequenz folgen(seit C++26) | ||
| initializer | - | ein Initialisierer (siehe unten) |
initializer kann einer der folgenden sein
= Ausdruck |
(1) | ||||||||
{ expression } |
(2) | ||||||||
( expression ) |
(3) | ||||||||
| expression | - | beliebiger Ausdruck (außer nicht geklammerte Komma-Ausdrücke) |
Eine strukturierte Bindungsdeklaration führt alle Bezeichner in der sb-identifier-list als Namen im umgebenden Geltungsbereich ein und bindet sie an Unterobjekte oder Elemente des durch expression bezeichneten Objekts. Die so eingeführten Bindungen werden strukturierte Bindungen genannt.
|
Einer der Bezeichner in der sb-identifier-list kann ein Ellipsensymbol vorangestellt sein. Ein solcher Bezeichner führt ein strukturierte Bindungspack ein. Der Bezeichner muss eine vorlagenbezogene Entität deklarieren. |
(seit C++26) |
Eine strukturierte Bindung ist ein Bezeichner in der sb-identifier-list der kein Ellipsensymbol vorangestellt ist, oder ein Element eines strukturierte Bindungspacks, das in derselben Bezeichnerliste eingeführt wurde(seit C++26).
Inhalt |
[bearbeiten] Bindeprozess
Eine strukturierte Bindungsdeklaration führt zunächst eine Variable mit eindeutigem Namen (hier bezeichnet als e) ein, um den Wert des Initialisierers zu speichern, wie folgt:
- Wenn expression einen Array-Typ cv1
Ahat und keine ref-qualifier vorhanden ist, definieren Sie e als attr (optional) specifiersA e;, wobei specifiers eine Sequenz der Spezifizierer in decl-specifier-seq ist, mit Ausnahme von auto.
- Dann wird jedes Element von e aus dem entsprechenden Element von expression gemäß der Form von initializer initialisiert.
- Für die Initialisierersyntax (1) werden die Elemente kopierinitialisiert.
- Für die Initialisierersyntaxen (2,3) werden die Elemente direktinitialisiert.
- Andernfalls definieren Sie e als attr (optional) decl-specifier-seq ref-qualifier (optional)
einitializer ;.
Wir verwenden E, um den Typ des Bezeichnerausdrucks e zu bezeichnen (d. h. E ist äquivalent zu std::remove_reference_t<decltype((e))>).
Eine strukturierte Bindungsgröße von E ist die Anzahl der strukturierten Bindungen, die durch die strukturierte Bindungsdeklaration eingeführt werden müssen.
|
Die Anzahl der Bezeichner in sb-identifier-list muss gleich der strukturierten Bindungsgröße von |
(bis C++26) |
|
Gegeben die Anzahl der Bezeichner in sb-identifier-list als N und die strukturierte Bindungsgröße von
|
(seit 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 refer to x, y, z, respectively auto [d, ...e] = C(); // OK: d refers to x; ...e refers to y and z auto [...f, g] = C(); // OK: ...f refers x and y; g refers to z auto [h, i, j, ...k] = C(); // OK: the pack k is empty auto [l, m, n, o, ...p] = C(); // error: structured binding size is too small }
Eine strukturierte Bindungsdeklaration führt die Bindung auf eine von drei möglichen Arten durch, abhängig von E:
- Fall 1: Wenn
Eein Array-Typ ist, dann werden die Namen an die Array-Elemente gebunden. - Fall 2: Wenn
Eein Klassentyp ist, der kein Union ist, und std::tuple_size<E> ein vollständiger Typ mit einem Member namensvalueist (unabhängig vom Typ oder der Zugänglichkeit eines solchen Members), dann wird das "tupelähnliche" Bindungsprotokoll verwendet. - Fall 3: Wenn
Eein Klassentyp ist, der kein Union ist, aber std::tuple_size<E> kein vollständiger Typ ist, dann werden die Namen an die zugänglichen Datenmember vonEgebunden.
Jeder der drei Fälle wird unten detaillierter beschrieben.
Jede strukturierte Bindung hat einen Referenztyp, der in der untenstehenden Beschreibung definiert ist. Dieser Typ ist der Typ, der von decltype zurückgegeben wird, wenn er auf eine unparenthetische strukturierte Bindung angewendet wird.
[bearbeiten] Fall 1: Array binden
Jede strukturierte Bindung in der sb-identifier-list wird zum Namen eines lvalue, das auf das entsprechende Element des Arrays verweist. Die strukturierte Bindungsgröße von E ist gleich der Anzahl der Array-Elemente.
Der Referenztyp für jede strukturierte Bindung ist der Array-Elementtyp. Beachten Sie, dass, wenn der Array-Typ E cv-qualifiziert ist, dies auch für seinen Elementtyp gilt.
int a[2] = {1, 2}; auto [x, y] = a; // creates e[2], copies a into e, // then x refers to e[0], y refers to e[1] auto& [xr, yr] = a; // xr refers to a[0], yr refers to a[1]
[bearbeiten] Fall 2: Typ binden, der Tupeloperationen implementiert
Der Ausdruck std::tuple_size<E>::value muss ein wohlgeformter ganzzahliger konstanten Ausdruck sein, und die strukturierte Bindungsgröße von E ist gleich std::tuple_size<E>::value.
Für jede strukturierte Bindung wird eine Variable mit dem Typ "Referenz auf std::tuple_element<I, E>::type" eingeführt: lvalue-Referenz, wenn ihr entsprechender Initialisierer ein lvalue ist, andernfalls rvalue-Referenz. Der Initialisierer für die I-te Variable ist
- e.get<I>(), wenn die Suche nach dem Bezeichner
getim Geltungsbereich vonEmittels Klassenmember-Zugriffssuche mindestens eine Deklaration findet, die eine Funktion-Template ist, deren erster Template-Parameter ein Nicht-Typ-Parameter ist - Andernfalls get<I>(e), wobei get nur mittels argumentabhängiger Suche (ADL) gesucht wird, wobei Nicht-ADL-Suchen ignoriert werden.
In diesen Initialisiererausdrücken ist e ein lvalue, wenn der Typ der Entität e eine lvalue-Referenz ist (dies geschieht nur, wenn der ref-qualifier & ist oder wenn er && ist und der Initialisiererausdruck ein lvalue ist) und andernfalls ein xvalue (dies führt effektiv eine Art Perfect Forwarding durch). I ist ein std::size_t PRvalue und <I> wird immer als Template-Parameterliste interpretiert.
Die Variable hat die gleiche Speicherdauer wie e.
Die strukturierte Bindung wird dann zum Namen eines lvalue, das auf das mit besagter Variable gebundene Objekt verweist.
Der Referenztyp für die I-te strukturierte Bindung ist 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 names a structured binding that refers to x (initialized from get<0>(tpl)) // decltype(a) is std::tuple_element<0, Tpl>::type, i.e. float& // b names a structured binding that refers to y (initialized from get<1>(tpl)) // decltype(b) is std::tuple_element<1, Tpl>::type, i.e. char&& // c names a structured binding that refers to the third component of tpl, get<2>(tpl) // decltype(c) is std::tuple_element<2, Tpl>::type, i.e. const int
[bearbeiten] Fall 3: An Datenmember binden
Jeder nicht-statische Datenmember von E muss ein direkter Member von E oder eine Basisklasse von E sein und muss im Kontext der strukturierten Bindung wohlgeformt sein, wenn er als e.name benannt wird. E darf keinen anonymen Union-Member haben. Die strukturierte Bindungsgröße von E ist gleich der Anzahl der nicht-statischen Datenmember.
Jede strukturierte Bindung in sb-identifier-list wird zum Namen eines lvalue, das auf den nächsten Member von e in Deklarationsreihenfolge verweist (Bitfelder werden unterstützt); der Typ des lvalue ist der von e.mI, wobei mI sich auf den I-ten Member bezieht.
Der Referenztyp der I-ten strukturierten Bindung ist der Typ von e.mI, wenn dieser kein Referenztyp ist, oder der deklarierte Typ von mI andernfalls.
#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 is an int lvalue identifying the 2-bit bit-field // y is a const volatile double lvalue std::cout << x << ' ' << y << '\n'; // 1 2.3 x = -2; // OK // y = -2.; // Error: y is const-qualified std::cout << x << ' ' << y << '\n'; // -2 2.3 }
[bearbeiten] Initialisierungsreihenfolge
Sei valI das Objekt oder die Referenz, die durch die I-te strukturierte Bindung in sb-identifier-list benannt wird.
- Die Initialisierung von e ist sequenziert vor der Initialisierung jedes valI.
- Die Initialisierung jedes valI ist sequenziert vor der Initialisierung jedes valJ, wobei I kleiner als J ist.
[bearbeiten] Hinweise
|
Strukturierte Bindungen können nicht eingeschränkt werden. template<class T> concept C = true; C auto [x, y] = std::pair{1, 2}; // error: constrained |
(seit C++20) |
Die Suche nach dem Member get ignoriert wie üblich die Zugänglichkeit und auch den exakten Typ des Nicht-Typ-Template-Parameters. Ein privater template<char*> void get(); Member führt zur Verwendung der Member-Interpretation, auch wenn diese fehlerhaft ist.
Der Teil der Deklaration vor [ gilt für die versteckte Variable e, nicht für die eingeführten strukturierten Bindungen.
Die Tupel-ähnliche Interpretation wird immer verwendet, wenn std::tuple_size<E> ein vollständiger Typ mit einem Member namens value ist, auch wenn dies dazu führen würde, dass das Programm fehlerhaft ist.
struct A { int x; }; namespace std { template<> struct tuple_size<::A> { void value(); }; } auto [x] = A{}; // error; the "data member" interpretation is not considered.
Die üblichen Regeln für die Referenzbindung an temporäre Objekte (einschließlich Lebensdauerverlängerung) gelten, wenn ein ref-qualifier vorhanden ist und der expression ein PRvalue ist. In diesen Fällen ist die versteckte Variable e eine Referenz, die an die temporäre Variable bindet, die aus dem PRvalue-Ausdruck materialisiert wurde, und ihre Lebensdauer verlängert. Wie üblich schlägt die Bindung fehl, wenn e eine nicht-const lvalue-Referenz ist.
int a = 1; const auto& [x] = std::make_tuple(a); // OK, not dangling auto& [y] = std::make_tuple(a); // error, cannot bind auto& to rvalue std::tuple auto&& [z] = std::make_tuple(a); // also OK
decltype(x), wobei x eine strukturierte Bindung bezeichnet, nennt den Referenztyp dieser strukturierten Bindung. Im Tupel-ähnlichen Fall ist dies der von std::tuple_element zurückgegebene Typ, der möglicherweise keine Referenz ist, obwohl in diesem Fall immer eine versteckte Referenz eingeführt wird. Dies emuliert effektiv das Verhalten der Bindung an eine Struktur, deren nicht-statische Datenmember die von std::tuple_element zurückgegebenen Typen haben, wobei die Referenzhaftigkeit der Bindung selbst nur ein Implementierungsdetail ist.
std::tuple<int, int&> f(); auto [x, y] = f(); // decltype(x) is int // decltype(y) is int& const auto [z, w] = f(); // decltype(z) is const int // decltype(w) is int&
|
Strukturierte Bindungen können nicht von Lambda-Ausdrücken erfasst werden. #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); } |
(bis C++20) |
|
Eine strukturierte Bindungsgröße darf 0 betragen, solange die sb-identifier-list genau einen Bezeichner enthält, der nur ein leeres strukturierte Bindungspack einführen kann. auto return_empty() -> std::tuple<>; template <class> void test_empty() { auto [] = return_empty(); // error auto [...args] = return_empty(); // OK, args is an empty pack auto [one, ...rest] = return_empty(); // error, structured binding size is too small } |
(seit C++26) |
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_structured_bindings |
201606L |
(C++17) | Strukturierte Bindungen |
202403L |
(C++26) | Strukturierte Bindungen mit Attributen | |
202406L |
(C++26) | Strukturierte Bindungsdeklaration als Bedingung | |
202411L |
(C++26) | Strukturierte Bindungen können ein Pack einführen |
[bearbeiten] Schlüsselwörter
[bearbeiten] Beispiel
#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 << "Insert is successful. The value is " << std::quoted(*iter) << ".\n"; else std::cout << "The value " << std::quoted(*iter) << " already exists in the set.\n"; } struct BitFields { // C++20: default member initializer for bit-fields 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'; } }
Ausgabe
Insert is successful. The value is "Hello". The value "Hello" already exists in the set. 1 2 3 4 4 3 2 1 1 2 3 4 4 3 2 1
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 2285 | C++17 | expression könnte sich auf die Namen aus identifier-list beziehen | die Deklaration ist ill-formed in diesem Fall |
| CWG 2312 | C++17 | die Bedeutung von mutable ging in Fall 3 verloren | seine Bedeutung ist immer noch erhalten |
| CWG 2313 | C++17 | in Fall 2 könnten die strukturierte Bindungsvariablen neu deklariert werden | können nicht neu deklariert werden |
| CWG 2339 | C++17 | in Fall 2 fehlte die Definition von I | die Definition wurde hinzugefügt |
| CWG 2341 (P1091R3) |
C++17 | strukturierte Bindungen konnten nicht mit statischer Speicherdauer deklariert werden |
erlaubt |
| CWG 2386 | C++17 | das "tupelähnliche" Bindungsprotokoll wurde verwendet jedes Mal, wenn std::tuple_size<E> ein vollständiger Typ ist |
nur verwendet, wenn std::tuple_size<E> einen Member value hat |
| CWG 2506 | C++17 | wenn expression vom Typ eines cv-qualifizierten Arrays ist, wurde die cv-Qualifizierung auf E übertragen |
verwirft diese cv-Qualifizierung |
| CWG 2635 | C++20 | strukturierte Bindungen könnten eingeschränkt werden | verboten |
| CWG 2867 | C++17 | die Initialisierungsreihenfolge war unklar | wurde klargestellt |
| P0961R1 | C++17 | in Fall 2 wurde der Member get verwendetwenn die Suche eine get jeglicher Art findet |
nur wenn die Suche eine Funktion findet Template mit einem Nicht-Typ-Parameter |
| P0969R0 | C++17 | in Fall 3 mussten die Member öffentlich sein | nur zugänglich sein müssen im Kontext der Deklaration |
[bearbeiten] Referenzen
- C++23 Standard (ISO/IEC 14882:2024)
- 9.6 Strukturierte Bindungsdeklarationen [dcl.struct.bind] (S. 228-229)
- C++20 Standard (ISO/IEC 14882:2020)
- 9.6 Strukturierte Bindungsdeklarationen [dcl.struct.bind] (S. 219-220)
- C++17 Standard (ISO/IEC 14882:2017)
- 11.5 Strukturierte Bindungsdeklarationen [dcl.struct.bind] (S. 219-220)
[bearbeiten] Siehe auch
| (C++11) |
erstellt ein tuple von Lvalue-Referenzen oder entpackt ein Tupel in einzelne Objekte (Funktionsvorlage) |