Range-based
for
loop
(since C++11)
Führt eine for -Schleife über einen Bereich aus.
Wird als besser lesbares Äquivalent zur traditionellen for Schleife verwendet, die über einen Wertebereich operiert, wie beispielsweise alle Elemente in einem Container.
Inhaltsverzeichnis |
Syntax
attr
(optional)
for (
init-statement
(optional)
item-declaration
:
range-initializer
)
statement
|
|||||||||
| attr | - | beliebige Anzahl von Attributen | ||
| init-statement | - |
(seit C++20)
eines der Folgenden
Beachten Sie, dass jede init-statement mit einem Semikolon enden muss. Deshalb wird sie informell oft als Ausdruck oder Deklaration gefolgt von einem Semikolon beschrieben. |
||
| item-declaration | - | eine Deklaration für jedes Range-Element | ||
| range-initializer | - | ein Ausdruck oder eine geschweifte Klammer-Initialisiererliste | ||
| statement | - | eine beliebige Anweisung (typischerweise eine zusammengesetzte Anweisung) |
Erklärung
Die obige Syntax erzeugt Code, der dem Folgenden entspricht außer der Lebensdauerverlängerung von Temporaries des range-initializer (siehe unten ) (since C++23) (die Variablen und Ausdrücke in /* */ sind nur zur Veranschaulichung):
|
|
(bis C++17) |
|
|
(seit C++17)
(bis C++20) |
|
|
(seit C++20) |
range-initializer wird ausgewertet, um die Sequenz oder den Bereich für die Iteration zu initialisieren. Jedes Element der Sequenz wird wiederum dereferenziert und dazu verwendet, die Variable mit dem in item-declaration angegebenen Typ und Namen zu initialisieren.
item-declaration kann eines der folgenden sein:
- eine einfache Deklaration mit folgenden Einschränkungen:
- Sie hat nur einen Deklarator .
- Der Deklarator darf keinen Initialisierer haben.
- Die Deklarationsspezifizierer-Sequenz darf nur Typspezifizierer und constexpr enthalten und kann keine Klasse oder Enumeration definieren.
Nur-Darstellungsausdrücke /* begin-expr */ und /* end-expr */ werden wie folgt definiert:
-
Wenn der Typ von
/* range */
ein Verweis auf einen Array-Typ
Rist:
-
-
Wenn
Rein Array mit fester Größe N ist, /* begin-expr */ ist /* range */ und /* end-expr */ ist /* range */ + N . -
Wenn
Rein Array mit unbekannter Größe oder ein Array mit unvollständigem Typ ist, ist das Programm fehlerhaft.
-
Wenn
-
Wenn der Typ von
/* range */
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 */ gleich /* range */ . begin ( ) und /* end-expr */ ist gleich /* range */ . end ( ) . -
Andernfalls ist
/* begin-expr */
gleich
begin
(
/* range */
)
und
/* end-expr */
ist gleich
end
(
/* range */
)
, wobei "
begin" und "end" mittels argument-dependent lookup gefunden werden (non-ADL lookup wird nicht durchgeführt).
Wenn die Schleife innerhalb der statement beendet werden muss, kann eine break statement als abschließende Anweisung verwendet werden.
Wenn die aktuelle Iteration innerhalb der Anweisung beendet werden muss, kann eine continue Anweisung als Abkürzung verwendet werden.
Wenn ein Name, der in der init-statement eingeführt wird, im äußersten Block der statement erneut deklariert wird, ist das Programm fehlerhaft:
for (int i : {1, 2, 3}) int i = 1; // Fehler: Neudeklaration
Temporärer Bereichsinitialisierer
Wenn der range-initializer ein temporäres Objekt zurückgibt, wird seine Lebensdauer bis zum Ende der Schleife verlängert, wie durch die Bindung an die Weiterleitungsreferenz /* range */ angezeigt.
Die Lebensdauern aller Temporaries innerhalb des range-initializer werden nicht verlängert es sei denn, sie würden sonst am Ende des range-initializer zerstört werden (since C++23) .
// wenn foo() als Wert zurückgibt for (auto& x : foo().items()) { /* ... */ } // bis C++23 undefiniertes Verhalten
|
Dieses Problem kann umgangen werden durch Verwendung einer Init-Anweisung : for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(seit C++20) |
|
Beachten Sie, dass selbst in C++23 Nicht-Referenzparameter von Zwischenfunktionsaufrufen keine Lebensdauerverlängerung erhalten (da sie in einigen ABIs im Aufgerufenen und nicht im Aufrufer zerstört werden), aber das ist nur ein Problem für Funktionen, die ohnehin 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) |
Hinweise
Wenn der range-initializer eine geschweifte Klammer-Initialisierungsliste ist, /* range */ wird als Referenz auf ein std::initializer_list abgeleitet.
Es ist sicher und in der Tat in generischem Code vorzuziehen, Deduktion für Forwarding-Referenzen zu verwenden, for ( auto && var : sequence ) .
Die Member-Interpretation wird verwendet, wenn der Bereichstyp einen Member namens "
begin
" und einen Member namens "
end
" besitzt. Dies erfolgt unabhängig davon, ob der Member ein Typ, ein Datenmember, eine Funktion oder ein Enumerator ist, und unabhängig von seiner Zugänglichkeit. Daher kann eine Klasse wie
class
meow
{
enum
{
begin
=
1
, end
=
2
}
;
/* rest of class */
}
;
nicht mit der bereichsbasierten
for
-Schleife verwendet werden, selbst wenn die namespace-weiten "
begin
"/"
end
"-Funktionen vorhanden sind.
Während die in der item-declaration deklarierte Variable normalerweise in der statement verwendet wird, ist dies nicht zwingend erforderlich.
|
Seit C++17 müssen die Typen von /* begin-expr */ und /* end-expr */ nicht mehr identisch sein, und tatsächlich muss der Typ von /* end-expr */ kein Iterator sein: Es muss lediglich eine Ungleichheitsvergleich mit einem Iterator möglich sein. Dies ermöglicht es, einen Bereich durch ein Prädikat zu begrenzen (z.B. "der Iterator zeigt auf ein Nullzeichen"). |
(since C++17) |
Bei Verwendung mit einem (nicht-konstanten) Objekt, das Copy-on-Write-Semantik besitzt, kann die bereichsbasierte
for
Schleife ein tiefes Kopieren auslösen, indem sie (implizit) die nicht-konstante
begin()
Memberfunktion aufruft.
|
Falls dies unerwünscht ist (beispielsweise weil die Schleife das Objekt nicht tatsächlich verändert), kann dies durch 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-Test-Makro | 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) | Lebensdauerverlängerung für alle temporären Objekte in Bereichsinitialisierer |
Schlüsselwörter
Beispiel
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // Zugriff per const-Referenz std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // Zugriff per Wert, der Typ von i ist int std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // Zugriff per Forwarding-Referenz, der Typ von i ist int& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // Zugriff per Forwarding-Referenz, der Typ von i ist const int& std::cout << i << ' '; std::cout << '\n'; for (int n : {0, 1, 2, 3, 4, 5}) // Der Initialisierer kann eine // geschweifte Initialisierungsliste sein std::cout << n << ' '; std::cout << '\n'; int a[] = {0, 1, 2, 3, 4, 5}; for (int n : a) // Der Initialisierer kann ein Array sein std::cout << n << ' '; std::cout << '\n'; for ([[maybe_unused]] int n : a) std::cout << 1 << ' '; // Die Schleifenvariable muss nicht verwendet werden std::cout << '\n'; for (auto n = v.size(); auto i : v) // Die Init-Anweisung (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // Typdefinition als Init-Anweisung (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // Alias-Deklaration als Init-Anweisung (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
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Veröffentlichtes Verhalten | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 1442 | C++11 |
es war nicht spezifiziert, ob die Suche nach Nicht-Mitglied-
"
begin
" und "
end
" die übliche unqualifizierte Suche einschließt
|
keine übliche unqualifizierte Suche |
| CWG 2220 | C++11 | die in init-statement eingeführten Namen konnten erneut deklariert werden | das Programm ist in diesem Fall fehlerhaft |
| CWG 2825 | C++11 |
wenn
range-initializer
eine geschweifte Initialisierungsliste ist,
werden die Nicht-Mitglied-"
begin
" und "
end
" gesucht
|
sucht in diesem Fall Mitglied-"
begin
"
und "
end
"
|
| P0962R1 | C++11 |
Mitglied-Interpretation wurde verwendet, wenn entweder
Mitglied-"
begin
" oder "
end
" vorhanden ist
|
wird nur verwendet, wenn beide vorhanden sind |
Siehe auch
|
wendet ein unäres
Funktionsobjekt
auf Elemente eines
Bereichs
an
(Funktions-Template) |