Range-based
for
loop
(since C++11)
Exécute une boucle for sur une plage.
Utilisé comme un équivalent plus lisible à la boucle for traditionnelle opérant sur une plage de valeurs, comme tous les éléments d'un conteneur.
Table des matières |
Syntaxe
attr
(optionnel)
for (
init-statement
(optionnel)
item-declaration
:
range-initializer
)
statement
|
|||||||||
| attr | - | tout nombre d' attributs | ||
| init-statement | - |
(depuis C++20)
l'un des éléments suivants
Notez que toute init-statement doit se terminer par un point-virgule. C'est pourquoi elle est souvent décrite informellement comme une expression ou une déclaration suivie d'un point-virgule. |
||
| item-declaration | - | une déclaration pour chaque élément de la plage | ||
| range-initializer | - | une expression ou une liste d'initialisation entre accolades | ||
| statement | - | toute instruction (généralement une instruction composée) |
Explication
La syntaxe ci-dessus produit un code équivalent à ce qui suit sauf pour l'extension de durée de vie des temporaires de range-initializer (voir ci-dessous ) (depuis C++23) (les variables et expressions encapsulées dans /* */ sont uniquement à titre d'exposition) :
|
|
(jusqu'en C++17) |
|
|
(depuis C++17)
(jusqu'en C++20) |
|
|
(depuis C++20) |
range-initializer est évalué pour initialiser la séquence ou la plage à itérer. Chaque élément de la séquence, à son tour, est déréférencé et est utilisé pour initialiser la variable avec le type et le nom donnés dans item-declaration .
item-declaration peut être l'un des éléments suivants :
- une déclaration simple avec les restrictions suivantes :
- Elle ne possède qu'un seul déclarateur .
- Le déclarateur ne doit pas avoir d' initialiseur .
- La séquence de spécificateurs de déclaration ne peut contenir que des spécificateurs de type et constexpr , et elle ne peut pas définir une classe ou une énumération .
Les expressions d'exposition uniquement /* begin-expr */ et /* end-expr */ sont définies comme suit :
-
Si le type de
/* range */
est une référence à un type tableau
R:
-
-
Si
Rest de taille N , /* begin-expr */ est /* range */ et /* end-expr */ est /* range */ + N . -
Si
Rest un tableau de taille inconnue ou un tableau de type incomplet, le programme est mal formé.
-
Si
-
Si le type de
/* range */
est une référence à un type de classe
C, et que les recherches dans la portée deCpour les noms «begin» et «end» trouvent chacune au moins une déclaration, alors /* begin-expr */ est /* range */ . begin ( ) et /* end-expr */ est /* range */ . end ( ) . -
Sinon,
/* begin-expr */
est
begin
(
/* range */
)
et
/* end-expr */
est
end
(
/* range */
)
, où «
begin» et «end» sont trouvés via la recherche dépendante des arguments (la recherche non-ADL n'est pas effectuée).
Si la boucle doit être interrompue au sein de statement , une break statement peut être utilisée comme instruction de terminaison.
Si l'itération actuelle doit être interrompue au sein de statement , une continue statement peut être utilisée comme raccourci.
Si un nom introduit dans init-statement est redéclaré dans le bloc le plus externe de statement , le programme est incorrect :
for (int i : {1, 2, 3}) int i = 1; // erreur : redéclaration
Initialiseur de plage temporaire
Si range-initializer retourne un temporaire, sa durée de vie est étendue jusqu'à la fin de la boucle, comme indiqué par la liaison à la référence de transfert /* range */ .
Les durées de vie de tous les temporaires dans range-initializer ne sont pas prolongées à moins qu'ils ne soient autrement détruits à la fin du range-initializer (depuis C++23) .
// si foo() retourne par valeur for (auto& x : foo().items()) { /* ... */ } // jusqu'à C++23 comportement indéfini
|
Ce problème peut être contourné en utilisant init-statement : for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(depuis C++20) |
|
Notez que même en C++23, les paramètres non-référence des appels de fonction intermédiaires ne bénéficient pas d'une extension de durée de vie (car dans certaines ABI, ils sont détruits dans la fonction appelée, pas dans l'appelant), mais ceci n'est un problème que pour les fonctions qui sont de toute façon boguées : 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 } |
(depuis C++23) |
Notes
Si le initialisateur-de-plage est une liste d'initialisation entre accolades , /* range */ est déduit comme étant une référence à un std::initializer_list .
Il est sûr, et en fait préférable dans le code générique, d'utiliser la déduction pour les références de transfert, for ( auto && var : sequence ) .
L'interprétation par membre est utilisée si le type de la plage possède un membre nommé «
begin
» et un membre nommé «
end
». Ceci est effectué indépendamment du fait que le membre soit un type, un membre de données, une fonction ou un énumérateur, et indépendamment de son accessibilité. Ainsi, une classe comme
class
meow
{
enum
{
begin
=
1
, end
=
2
}
;
/* reste de la classe */
}
;
ne peut pas être utilisée avec la boucle
for
basée sur les plages, même si les fonctions «
begin
»/«
end
» dans la portée de l'espace de noms sont présentes.
Bien que la variable déclarée dans la item-declaration soit généralement utilisée dans la statement , cela n'est pas obligatoire.
|
Depuis C++17, les types de /* begin-expr */ et de /* end-expr */ n'ont pas besoin d'être identiques, et en fait le type de /* end-expr */ n'a pas besoin d'être un itérateur : il doit seulement pouvoir être comparé par inégalité avec un. Cela permet de délimiter une plage par un prédicat (par exemple, "l'itérateur pointe vers un caractère nul"). |
(depuis C++17) |
Lorsqu'il est utilisé avec un objet (non-const) qui a une sémantique de copie à l'écriture, la boucle
for
basée sur une plage peut déclencher une copie profonde en appelant (implicitement) la fonction membre non-const
begin()
.
|
Si cela est indésirable (par exemple parce que la boucle ne modifie pas réellement l'objet), cela peut être évité en utilisant std::as_const : 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)) { /* ... */ } |
(depuis C++17) |
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_range_based_for
|
200907L
|
(C++11) | Boucle for basée sur des plages |
201603L
|
(C++17) |
Boucle
for
basée sur des plages avec
types
begin
/
end
différents
|
|
202211L
|
(C++23) | Extension de durée de vie pour tous les objets temporaires dans l'initialiseur de plage |
Mots-clés
Exemple
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // accès par référence constante std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // accès par valeur, le type de i est int std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // accès par référence de transfert, le type de i est int& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // accès par référence de transfert, le type de i est const int& std::cout << i << ' '; std::cout << '\n'; for (int n : {0, 1, 2, 3, 4, 5}) // l'initialiseur peut être une // liste d'initialisation entre accolades std::cout << n << ' '; std::cout << '\n'; int a[] = {0, 1, 2, 3, 4, 5}; for (int n : a) // l'initialiseur peut être un tableau std::cout << n << ' '; std::cout << '\n'; for ([[maybe_unused]] int n : a) std::cout << 1 << ' '; // la variable de boucle n'a pas besoin d'être utilisée std::cout << '\n'; for (auto n = v.size(); auto i : v) // l'instruction d'initialisation (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // déclaration typedef comme instruction d'initialisation (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // déclaration d'alias comme instruction d'initialisation (C++23) std::cout << i << ' '; std::cout << '\n'; }
Sortie :
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
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | S'applique à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 1442 | C++11 |
il n'était pas spécifié si la recherche des non-membres
"
begin
" et "
end
" incluait la recherche non qualifiée usuelle
|
pas de recherche non qualifiée usuelle |
| CWG 2220 | C++11 | les noms introduits dans init-statement pouvaient être redéclarés | le programme est mal formé dans ce cas |
| CWG 2825 | C++11 |
si
range-initializer
est une liste d'initialisation entre accolades,
les non-membres "
begin
" et "
end
" seront recherchés
|
recherchera les membres "
begin
"
et "
end
" dans ce cas
|
| P0962R1 | C++11 |
l'interprétation membre était utilisée si l'un ou l'autre
des membres "
begin
" et "
end
" était présent
|
utilisé uniquement si les deux sont présents |
Voir aussi
|
applique un
function object
unaire aux éléments d'un
range
(modèle de fonction) |