Namensräume
Varianten
Aktionen

Bereichsbasierte for-Schleife (seit C++11)

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

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:
(seit C++23)

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)

{

auto&& /* Bereich */ = range-initializer ;
for (auto /* begin */ = /* begin-expr */, /* end */ = /* end-expr */;
/* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
Anweisung
}

}

(bis C++17)

{

auto&& /* Bereich */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
Anweisung
}

}

(seit C++17)
(bis C++20)

{

init-statement
auto&& /* Bereich */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
Anweisung
}

}

(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:

Exposition-only Ausdrücke /* begin-expr */ und /* end-expr */ sind wie folgt definiert:

  • Wenn der Typ von /* Bereich */ ein Referenz auf einen Array-Typ R ist
  • Wenn R eine feste Bindung N hat, ist /* begin-expr */ /* Bereich */ und /* end-expr */ ist /* Bereich */ + N.
  • Wenn R ein Array unbekannter Bindung oder ein Array unvollständigen Typs ist, ist das Programm schlecht geformt.
  • Wenn der Typ von /* Bereich */ 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 */ /* 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

for

[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) [bearbeiten]