Bereichsbasierte for-Schleife (seit C++11)
Führt eine for-Schleife über einen Bereich aus.
Wird als lesbarere Entsprechung zur traditionellen for-Schleife verwendet, die über einen Wertebereich iteriert, wie z. B. alle Elemente in einem Container.
Inhalt |
[bearbeiten] Syntax
attr (optional) for ( init-statement (optional) item-declaration : range-initializer ) statement |
|||||||||
| attr | - | beliebige Anzahl von Attributen | ||
| init-statement | - | (seit C++20) eine der folgenden:
Beachten Sie, dass jede init-statement mit einem Semikolon enden muss. Deshalb wird sie oft informell als Ausdruck oder Deklaration gefolgt von einem Semikolon beschrieben. | ||
| item-declaration | - | eine Deklaration für jedes Element im Bereich | ||
| range-initializer | - | ein Ausdruck oder eine klammerumschlossene Initialisierungsliste | ||
| Anweisung | - | beliebige Anweisung (typischerweise eine zusammengesetzte Anweisung) |
[bearbeiten] Erklärung
Die obige Syntax erzeugt Code, der äquivalent zu folgendem ist mit Ausnahme der Lebensdauererweiterung von Temporärdateien von range-initializer (siehe unten)(seit C++23) (die Variablen und Ausdrücke, die in /* */ eingeschlossen sind, dienen nur zur Veranschaulichung)
|
|
(bis C++17) |
|
|
(seit C++17) (bis C++20) |
|
|
(seit C++20) |
range-initializer wird ausgewertet, um die Sequenz oder den zu iterierenden Bereich zu initialisieren. Jedes Element der Sequenz wird nacheinander dereferenziert und zur Initialisierung der Variablen mit dem in item-declaration angegebenen Typ und Namen verwendet.
item-declaration kann eine der folgenden sein:
- eine einfache Deklaration mit den folgenden Einschränkungen:
- Sie hat nur einen Deklarator.
- Der Deklarator darf keinen Initialisierer haben.
- Die Sequenz von Deklarationsspezifizierern darf nur Typspezifizierer und constexpr enthalten und darf keine Klasse oder Aufzählung definieren.
Exposition-only Ausdrücke /* begin-expr */ und /* end-expr */ sind wie folgt definiert:
- Wenn der Typ von /* Bereich */ ein Referenz auf einen Array-Typ
Rist
- Wenn
Reine feste Bindung N hat, ist /* begin-expr */ /* Bereich */ und /* end-expr */ ist /* Bereich */ + N. - Wenn
Rein Array unbekannter Bindung oder ein Array unvollständigen Typs ist, ist das Programm schlecht geformt.
- Wenn
- Wenn der Typ von /* Bereich */ eine Referenz auf einen Klassentyp
Cist und die Suche im Gültigkeitsbereich vonCnach den Namen „begin“ und „end“ jeweils mindestens eine Deklaration findet, dann ist /* begin-expr */ /* Bereich */.begin() und /* end-expr */ ist /* Bereich */.end(). - Andernfalls ist /* begin-expr */ begin(/* Bereich */) und /* end-expr */ ist end(/* Bereich */), wobei „
begin“ und „end“ über argumentabhängige Suche gefunden werden (keine Nicht-ADL-Suche wird durchgeführt).
Wenn die Schleife innerhalb von statement beendet werden muss, kann eine break-Anweisung als beendende Anweisung verwendet werden.
Wenn die aktuelle Iteration innerhalb von statement beendet werden muss, kann eine continue-Anweisung als Abkürzung verwendet werden.
Wenn ein Name, der in init-statement eingeführt wurde, im äußersten Block von statement neu deklariert wird, ist das Programm schlecht geformt.
for (int i : {1, 2, 3}) int i = 1; // error: redeclaration
[bearbeiten] Temporärer Bereichsinitialisierer
Wenn range-initializer eine temporäre Datei zurückgibt, wird ihre Lebensdauer bis zum Ende der Schleife verlängert, wie durch die Bindung an die Forwarding-Referenz /* Bereich */ angezeigt.
Die Lebensdauer aller temporären Objekte innerhalb von range-initializer wird nicht verlängert es sei denn, sie würden sonst am Ende von range-initializer zerstört werden(seit C++23).
// if foo() returns by value for (auto& x : foo().items()) { /* ... */ } // until C++23 undefined behavior
|
Dieses Problem kann mit init-statement umgangen werden. for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(seit C++20) |
|
Beachten Sie, dass selbst in C++23 keine Lebensdauererweiterung für Nicht-Referenz-Parameter von Zwischenfunktionsaufrufen erfolgt (da sie in einigen ABIs im Aufgerufenen und nicht im Aufrufer zerstört werden), aber das ist nur ein Problem für Funktionen, die sowieso fehlerhaft sind. using T = std::list<int>; const T& f1(const T& t) { return t; } const T& f2(T t) { return t; } // always returns a dangling reference T g(); void foo() { for (auto e : f1(g())) {} // OK: lifetime of return value of g() extended for (auto e : f2(g())) {} // UB: lifetime of f2's value parameter ends early } |
(seit C++23) |
[bearbeiten] Anmerkungen
Wenn der range-initializer eine klammerumschlossene Initialisierungsliste ist, wird /* Bereich */ zu einer Referenz auf eine std::initializer_list abgeleitet.
Es ist sicher und in der Tat vorzuziehen, in generischem Code die Ableitung zu einer Forwarding-Referenz zu verwenden: for (auto&& var : sequence).
Die Mitgliederinterpretation wird verwendet, wenn der Typ des Bereichs ein Mitglied namens „begin“ und ein Mitglied namens „end“ hat. Dies geschieht unabhängig davon, ob das Mitglied ein Typ, ein Datenmitglied, eine Funktion oder ein Enumerator ist und unabhängig von seiner Zugänglichkeit. Eine Klasse wie class meow { enum { begin = 1, end = 2 }; /* Rest der Klasse */ }; kann also nicht mit der bereichsbasierten for-Schleife verwendet werden, auch wenn Namespace-Scope „begin“/„end“-Funktionen vorhanden sind.
Obwohl die Variable, die in item-declaration deklariert wird, normalerweise in statement verwendet wird, ist dies keine zwingende Anforderung.
|
Ab C++17 müssen die Typen von /* begin-expr */ und /* end-expr */ nicht gleich sein, und tatsächlich muss der Typ von /* end-expr */ kein Iterator sein: Er muss lediglich ungleich einem Iterator verglichen werden können. Dies ermöglicht die Abgrenzung eines Bereichs durch ein Prädikat (z. B. „der Iterator zeigt auf ein Nullzeichen“). |
(seit C++17) |
Bei Verwendung mit einem (nicht-const) Objekt, das Copy-on-Write-Semantik aufweist, kann die bereichsbasierte for-Schleife eine tiefe Kopie auslösen, indem sie (implizit) die Nicht-const begin()-Mitgliedsfunktion aufruft.
|
Wenn dies unerwünscht ist (z. B. weil die Schleife das Objekt nicht tatsächlich modifiziert), kann es durch die Verwendung von std::as_const vermieden werden. struct cow_string { /* ... */ }; // a copy-on-write string cow_string str = /* ... */; // for (auto x : str) { /* ... */ } // may cause deep copy for (auto x : std::as_const(str)) { /* ... */ } |
(seit C++17) |
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_range_based_for |
200907L |
(C++11) | Bereichsbasierte for-Schleife
|
201603L |
(C++17) | Bereichsbasierte for-Schleife mit unterschiedlichen begin/end-Typen | |
202211L |
(C++23) | Lebensdauererweiterung für alle temporären Objekte in range-initializer |
[bearbeiten] Schlüsselwörter
[bearbeiten] Beispiel
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // access by const reference std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // access by value, the type of i is int std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // access by forwarding reference, the type of i is int& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // access by f-d reference, the type of i is const int& std::cout << i << ' '; std::cout << '\n'; for (int n : {0, 1, 2, 3, 4, 5}) // the initializer may be a // braced-enclosed initializer list std::cout << n << ' '; std::cout << '\n'; int a[] = {0, 1, 2, 3, 4, 5}; for (int n : a) // the initializer may be an array std::cout << n << ' '; std::cout << '\n'; for ([[maybe_unused]] int n : a) std::cout << 1 << ' '; // the loop variable need not be used std::cout << '\n'; for (auto n = v.size(); auto i : v) // the init-statement (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // typedef declaration as init-statement (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // alias declaration as init-statement (C++23) std::cout << i << ' '; std::cout << '\n'; }
Ausgabe
0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 1 1 1 1 1 1 5 5 5 5 5 5 0 1 2 3 4 5 0 1 2 3 4 5
[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 1442 | C++11 | Es war nicht spezifiziert, ob die Suche nach Nicht-Member „ begin“ und „end“ die übliche unqualifizierte Suche einschloss. |
Keine übliche unqualifizierte Suche |
| CWG 2220 | C++11 | Die in init-statement eingeführten Namen konnten neu deklariert werden. | Das Programm ist in diesem Fall schlecht geformt. |
| CWG 2825 | C++11 | Wenn range-initializer eine klammerumschlossene Initialisierungsliste ist, würden die Nicht-Member „ begin“ und „end“ gesucht. |
würde stattdessen die Mitglieder „begin“und „ end“ in diesem Fall verwenden. |
| P0962R1 | C++11 | Die Mitgliederinterpretation wurde verwendet, wenn entweder Mitglieder „ begin“ und „end“ vorhanden waren |
nur verwendet, wenn beide vorhanden waren. |
[bearbeiten] Siehe auch
| wendet ein unäres Funktionsobjekt auf Elemente aus einem Bereich an (Funktion-Template) |