Range-based
for
loop
(since C++11)
Ejecuta un bucle for sobre un rango.
Se utiliza como un equivalente más legible del tradicional for loop que opera sobre un rango de valores, como todos los elementos en un contenedor.
Contenidos |
Sintaxis
attr
(opcional)
for (
init-statement
(opcional)
item-declaration
:
range-initializer
)
statement
|
|||||||||
| attr | - | cualquier número de atributos | ||
| init-statement | - |
(desde C++20)
uno de
Nótese que cualquier init-statement debe terminar con un punto y coma. Por esto se describe informalmente como una expresión o una declaración seguida de un punto y coma. |
||
| item-declaration | - | una declaración para cada elemento del rango | ||
| range-initializer | - | una expresión o lista de inicialización entre llaves | ||
| statement | - | cualquier sentencia (normalmente una sentencia compuesta) |
Explicación
La sintaxis anterior produce código equivalente al siguiente excepto por la extensión de vida útil de los temporales del range-initializer (ver abajo ) (desde C++23) (las variables y expresiones envueltas en /* */ son solo para exposición):
|
|
(hasta C++17) |
|
|
(desde C++17)
(hasta C++20) |
|
|
(desde C++20) |
range-initializer se evalúa para inicializar la secuencia o rango a iterar. Cada elemento de la secuencia, a su vez, se desreferencia y se utiliza para inicializar la variable con el tipo y nombre dados en item-declaration .
item-declaration puede ser uno de los siguientes:
- una declaración simple con las siguientes restricciones:
- Tiene solo un declarador .
- El declarador no debe tener inicializador .
- La secuencia de especificadores de declaración solo puede contener especificadores de tipo y constexpr , y no puede definir una clase o enumeración .
Expresiones solo de exposición /* begin-expr */ y /* end-expr */ se definen de la siguiente manera:
-
Si el tipo de
/* range */
es una referencia a un tipo de arreglo
R:
-
-
Si
Res de límite N , /* begin-expr */ es /* range */ y /* end-expr */ es /* range */ + N . -
Si
Res un array de límite desconocido o un array de tipo incompleto, el programa está mal formado.
-
Si
-
Si el tipo de
/* range */
es una referencia a un tipo de clase
C, y las búsquedas en el ámbito deCpara los nombres "begin" y "end" encuentran al menos una declaración cada una, entonces /* begin-expr */ es /* range */ . begin ( ) y /* end-expr */ es /* range */ . end ( ) . -
En caso contrario,
/* begin-expr */
es
begin
(
/* range */
)
y
/* end-expr */
es
end
(
/* range */
)
, donde "
begin" y "end" se encuentran mediante búsqueda dependiente de argumento (no se realiza búsqueda no-ADL).
Si el bucle necesita ser terminado dentro de statement , se puede usar una break statement como sentencia de terminación.
Si la iteración actual necesita ser terminada dentro de statement , se puede usar una continue statement como atajo.
Si un nombre introducido en init-statement es redeclarado en el bloque más externo de statement , el programa está mal formado:
for (int i : {1, 2, 3}) int i = 1; // error: redeclaración
Inicializador de rango temporal
Si range-initializer retorna un temporal, su duración se extiende hasta el final del bucle, como se indica al vincularlo a la referencia de reenvío /* range */ .
Las vidas de todos los temporales dentro del range-initializer no se extienden a menos que de otro modo serían destruidos al final del range-initializer (since C++23) .
// si foo() retorna por valor for (auto& x : foo().items()) { /* ... */ } // hasta C++23 comportamiento indefinido
|
Este problema puede solucionarse utilizando init-statement : for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(desde C++20) |
|
Nótese que incluso en C++23 los parámetros no referencia de llamadas de función intermedias no obtienen una extensión de vida útil (porque en algunas ABIs se destruyen en la función llamada, no en la llamadora), pero eso solo es un problema para funciones que de todos modos tienen errores: 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 } |
(desde C++23) |
Notas
Si el range-initializer es una lista de inicialización entre llaves , /* range */ se deduce como una referencia a un std::initializer_list .
Es seguro, y de hecho, preferible en código genérico, usar deducción para referencias de reenvío, for ( auto && var : sequence ) .
La interpretación de miembro se utiliza si el tipo de rango tiene un miembro llamado "
begin
" y un miembro llamado "
end
". Esto se hace independientemente de si el miembro es un tipo, miembro de datos, función o enumerador, y sin importar su accesibilidad. Por lo tanto, una clase como
class
meow
{
enum
{
begin
=
1
, end
=
2
}
;
/* resto de la clase */
}
;
no puede utilizarse con el bucle
for
basado en rangos incluso si las funciones "
begin
"/"
end
" en el ámbito del espacio de nombres están presentes.
Aunque la variable declarada en la item-declaration normalmente se utiliza en la statement , no es obligatorio hacerlo.
|
A partir de C++17, los tipos de /* begin-expr */ y /* end-expr */ no tienen que ser iguales, y de hecho el tipo de /* end-expr */ no tiene que ser un iterador: solo necesita poder compararse por desigualdad con uno. Esto hace posible delimitar un rango mediante un predicado (por ejemplo, "el iterador apunta a un carácter nulo"). |
(since C++17) |
Cuando se utiliza con un objeto (no constante) que tiene semántica de copia en escritura, el bucle
for
basado en rangos puede desencadenar una copia profunda al llamar (implícitamente) a la función miembro no constante
begin()
.
|
Si eso es indeseable (por ejemplo, porque el bucle no está modificando realmente el objeto), puede evitarse usando 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)) { /* ... */ } |
(desde C++17) |
| Macro de prueba de características | Valor | Estándar | Característica |
|---|---|---|---|
__cpp_range_based_for
|
200907L
|
(C++11) | Bucle for basado en rangos |
201603L
|
(C++17) |
Bucle
for
basado en rangos con
tipos diferentes de
begin
/
end
|
|
202211L
|
(C++23) | Extensión de vida útil para todos los objetos temporales en inicializador-de-rango |
Palabras clave
Ejemplo
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // acceso por referencia constante std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // acceso por valor, el tipo de i es int std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // acceso por referencia de reenvío, el tipo de i es int& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // acceso por referencia f-d, el tipo de i es const int& std::cout << i << ' '; std::cout << '\n'; for (int n : {0, 1, 2, 3, 4, 5}) // el inicializador puede ser una // lista de inicialización entre llaves std::cout << n << ' '; std::cout << '\n'; int a[] = {0, 1, 2, 3, 4, 5}; for (int n : a) // el inicializador puede ser un array std::cout << n << ' '; std::cout << '\n'; for ([[maybe_unused]] int n : a) std::cout << 1 << ' '; // la variable del bucle no necesita ser usada std::cout << '\n'; for (auto n = v.size(); auto i : v) // la sentencia de inicialización (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // declaración typedef como sentencia de inicialización (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // declaración de alias como sentencia de inicialización (C++23) std::cout << i << ' '; std::cout << '\n'; }
Salida:
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
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.
| DR | Se aplica a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1442 | C++11 |
no estaba especificado si la búsqueda de no-miembros
"
begin
" y "
end
" incluye la búsqueda no calificada habitual
|
no se realiza búsqueda no calificada habitual |
| CWG 2220 | C++11 | los nombres introducidos en init-statement podían ser redeclarados | el programa está mal formado en este caso |
| CWG 2825 | C++11 |
si
range-initializer
es una lista de inicialización entre llaves,
se buscarán los no-miembros "
begin
" y "
end
"
|
buscará miembros "
begin
"
y "
end
" en este caso
|
| P0962R1 | C++11 |
se usaba la interpretación de miembro si cualquiera
de los miembros "
begin
" y "
end
" estaba presente
|
solo se usa si ambos están presentes |
Véase también
|
aplica un
function object
unario a elementos de un
range
(plantilla de función) |