Wird als besser lesbares Äquivalent zur traditionellen
for
Schleife
verwendet, die über einen Wertebereich operiert, wie beispielsweise alle Elemente in einem Container.
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.
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):
{
auto&&
/* range */
=
Bereichsinitialisierer
;
for (auto
/* begin */
=
/* begin-expr */
,
/* end */
=
/* end-expr */
;
/* begin */
!=
/* end */
; ++
/* begin */
)
{
Elementdeklaration
= *
/* begin */
;
Anweisung
}
}
(bis C++17)
{
auto&&
/* range */
=
Bereichsinitialisierer
;
auto
/* begin */
=
/* begin-expr */
;
auto
/* end */
=
/* end-expr */
;
for ( ;
/* begin */
!=
/* end */
; ++
/* begin */
)
{
Elementdeklaration
= *
/* begin */
;
Anweisung
}
}
(seit C++17)
(bis C++20)
{
Initialisierungsanweisung
auto&&
/* range */
=
Bereichsinitialisierer
;
auto
/* begin */
=
/* begin-expr */
;
auto
/* end */
=
/* end-expr */
;
for ( ;
/* begin */
!=
/* end */
; ++
/* begin */
)
{
Elementdeklaration
= *
/* begin */
;
Anweisung
}
}
(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.
Nur-Darstellungsausdrücke
/* begin-expr */
und
/* end-expr */
werden wie folgt definiert:
Wenn der Typ von
/* range */
ein Verweis auf einen Array-Typ
R
ist:
Wenn
R
ein Array mit fester Größe
N
ist,
/* begin-expr */
ist
/* range */
und
/* end-expr */
ist
/* range */
+
N
.
Wenn
R
ein Array mit unbekannter Größe oder ein Array mit unvollständigem Typ ist, ist das Programm fehlerhaft.
Wenn der Typ von
/* range */
eine Referenz auf einen Klassentyp
C
ist, und die Suche im Gültigkeitsbereich von
C
nach 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ückgibtfor(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() extendedfor(auto e : f2(g())){}// UB: lifetime of f2's value parameter ends early}
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 copyfor(auto x :std::as_const(str)){/* ... */}
#include <iostream>#include <vector>int main(){std::vector<int> v ={0, 1, 2, 3, 4, 5};for(constint& i : v)// Zugriff per const-Referenzstd::cout<< i <<' ';std::cout<<'\n';for(auto i : v)// Zugriff per Wert, der Typ von i ist intstd::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';constauto& 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 seinstd::cout<< n <<' ';std::cout<<'\n';int a[]={0, 1, 2, 3, 4, 5};for(int n : a)// Der Initialisierer kann ein Array seinstd::cout<< n <<' ';std::cout<<'\n';for([[maybe_unused]]int n : a)std::cout<<1<<' ';// Die Schleifenvariable muss nicht verwendet werdenstd::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';}