Considere un estándar para bucle:
for (int i = 0; i < 10; ++i)
{
// do something with i
}
Quiero evitar que la variable i
se modifique en el cuerpo del for
bucle.
Sin embargo, no puedo declararlo i
ya const
que esto invalida la declaración de incremento. ¿Hay alguna forma de hacer i
una const
variable fuera de la declaración de incremento?
const int i
argumento. La mutabilidad del índice solo se expone donde es necesario y puede usar lainline
palabra clave para que no tenga ningún efecto en la salida compilada.const
para empezar.Respuestas:
Desde c ++ 20, puede usar rangos :: vistas :: iota así:
for (int const i : std::views::iota(0, 10)) { std::cout << i << " "; // ok i = 42; // error }
Aquí tienes una demostración .
Desde c ++ 11, también puede usar la siguiente técnica, que usa un IIILE (expresión lambda en línea inmediatamente invocada):
int x = 0; for (int i = 0; i < 10; ++i) [&,i] { std::cout << i << " "; // ok, i is readable i = 42; // error, i is captured by non-mutable copy x++; // ok, x is captured by mutable reference }(); // IIILE
Aquí tienes una demostración .
Tenga en cuenta que eso
[&,i]
significa quei
se captura mediante una copia no mutable, y todo lo demás se captura mediante una referencia mutable. El();
al final del ciclo simplemente significa que el lambda se invoca inmediatamente.fuente
&
captura generalizada , lo que obligaría a capturar cada referencia explícitamente, lo que hace que esto sea bastante incómodo. También sospecho que esto podría conducir a errores fáciles en los que un autor se olvida()
, haciendo que el código nunca se invoque. Esto es lo suficientemente pequeño como para perderse en la revisión del código.[&]
porque entran en conflicto con los estándares de codificación como AUTOSAR (Regla A5-1-2), HIC ++ y creo que también MISRA (no estoy seguro). No es que no sea correcto; es que las organizaciones prohíben este tipo de código para cumplir con los estándares. En cuanto a()
, la versión más reciente de gcc no marca esto incluso con-Wextra
. Sigo pensando que el enfoque es ordenado; simplemente no funciona para muchas organizaciones.Para cualquiera a quien le guste la
std::views::iota
respuesta de Cigien pero no esté trabajando en C ++ 20 o superior, es bastante sencillo implementar una versión simplificada y liviana destd::views::iota
compatiblec ++ 11 o superior.Todo lo que requiere es:
operator++
yoperator*
) que envuelve un valor integral (por ejemplo, unint
)begin()
yend()
que devuelve los iteradores anteriores. Esto le permitirá trabajar enfor
bucles basados en rangos.Una versión simplificada de esto podría ser:
#include <iterator> // This is just a class that wraps an 'int' in an iterator abstraction // Comparisons compare the underlying value, and 'operator++' just // increments the underlying int class counting_iterator { public: // basic iterator boilerplate using iterator_category = std::input_iterator_tag; using value_type = int; using reference = int; using pointer = int*; using difference_type = std::ptrdiff_t; // Constructor / assignment constexpr explicit counting_iterator(int x) : m_value{x}{} constexpr counting_iterator(const counting_iterator&) = default; constexpr counting_iterator& operator=(const counting_iterator&) = default; // "Dereference" (just returns the underlying value) constexpr reference operator*() const { return m_value; } constexpr pointer operator->() const { return &m_value; } // Advancing iterator (just increments the value) constexpr counting_iterator& operator++() { m_value++; return (*this); } constexpr counting_iterator operator++(int) { const auto copy = (*this); ++(*this); return copy; } // Comparison constexpr bool operator==(const counting_iterator& other) const noexcept { return m_value == other.m_value; } constexpr bool operator!=(const counting_iterator& other) const noexcept { return m_value != other.m_value; } private: int m_value; }; // Just a holder type that defines 'begin' and 'end' for // range-based iteration. This holds the first and last element // (start and end of the range) // The begin iterator is made from the first value, and the // end iterator is made from the second value. struct iota_range { int first; int last; constexpr counting_iterator begin() const { return counting_iterator{first}; } constexpr counting_iterator end() const { return counting_iterator{last}; } }; // A simple helper function to return the range // This function isn't strictly necessary, you could just construct // the 'iota_range' directly constexpr iota_range iota(int first, int last) { return iota_range{first, last}; }
He definido lo anterior con
constexpr
dónde es compatible, pero para versiones anteriores de C ++ como C ++ 11/14, es posible que deba eliminarloconstexpr
donde no es legal en esas versiones para hacerlo.La plantilla anterior permite que el siguiente código funcione en versiones anteriores a C ++ 20:
for (int const i : iota(0, 10)) { std::cout << i << " "; // ok i = 42; // error }
Que generará el mismo ensamblado que la
std::views::iota
solución C ++ 20 y lafor
solución clásica -loop cuando se optimice.Esto funciona con cualquier compilador compatible con C ++ 11 (por ejemplo, compiladores como
gcc-4.9.4
) y aún produce un ensamblaje casi idéntico a unfor
equivalente básico de bucle.Nota: La
iota
función auxiliar es solo para la paridad de características con lastd::views::iota
solución C ++ 20 ; pero de manera realista, también podría construir directamente un eniota_range{...}
lugar de llamariota(...)
. El primero solo presenta una ruta de actualización fácil si un usuario desea cambiar a C ++ 20 en el futuro.fuente
int
y luego crea una clase de "rango" para devolver el comienzo / finLa versión KISS ...
for (int _i = 0; _i < 10; ++_i) { const int i = _i; // use i here }
Si su caso de uso es solo para evitar la modificación accidental del índice de bucle, esto debería hacer que el error sea obvio. (Si quieres evitar modificaciones intencionales , bueno, buena suerte ...)
fuente
_
. Y un poco de explicación (por ejemplo, alcance) sería útil. De lo contrario, sí, muy bien KISSy.i_
sería más compatible._i
que todavía se puede modificar en el bucle.std::views::iota
de una manera totalmente a prueba de balas. El texto de la respuesta explica sus limitaciones y cómo intenta responder la pregunta. Un montón de C ++ 11 demasiado complicado hace que la cura sea peor que la enfermedad en términos de fácil lectura y mantenimiento, en mi opinión. Esto sigue siendo muy fácil de leer para todos los que conocen C ++, y parece razonable como un idioma. (Pero debe evitar los nombres de subrayado inicial.)_Uppercase
y losdouble__underscore
identificadores están reservados._lowercase
los identificadores solo están reservados en el ámbito global.Si no tiene acceso a c ++ 20, cambio de imagen típico con una función
#include <vector> #include <numeric> // std::iota std::vector<int> makeRange(const int start, const int end) noexcept { std::vector<int> vecRange(end - start); std::iota(vecRange.begin(), vecRange.end(), start); return vecRange; }
ahora puedes
for (const int i : makeRange(0, 10)) { std::cout << i << " "; // ok //i = 100; // error }
( Ver una demostración )
Actualización : inspirado en el comentario de @ Human-Compiler , me preguntaba si las respuestas dadas tienen alguna diferencia en el caso del rendimiento. Resulta que, a excepción de este enfoque, todos los demás enfoques sorprendentemente tienen el mismo rendimiento (para el rango
[0, 10)
). Elstd::vector
enfoque es el peor.( Consulte Quick-Bench en línea )
fuente
vector
. Si el rango es muy grande, esto podría ser malo.std::vector
es bastante terrible en una escala relativa si el rango también es pequeño, y podría ser muy malo si se suponía que era un pequeño bucle interno que se ejecutaba muchas veces. Algunos compiladores (como clang con libc ++, pero no libstdc ++) pueden optimizar nuevo / eliminar de una asignación que no escapa a la función, pero de lo contrario, esta podría ser fácilmente la diferencia entre un pequeño bucle completamente desenrollado frente a una llamada anew
+delete
, y tal vez almacenarlo en esa memoria.const i
simplemente no vale la pena la sobrecarga en la mayoría de los casos, sin C ++ 20 formas que lo hacen barato. Especialmente con rangos de variables en tiempo de ejecución que hacen que sea menos probable que el compilador optimice todo.¿No podría simplemente mover parte o todo el contenido de su bucle for en una función que acepte i como una constante?
Es menos óptimo que algunas soluciones propuestas, pero si es posible, esto es bastante simple de hacer.
Editar: Solo un ejemplo, ya que tiendo a ser poco claro.
for (int i = 0; i < 10; ++i) { looper( i ); } void looper ( const int v ) { // do your thing here }
fuente
Y aquí hay una versión de C ++ 11:
for (int const i : {0,1,2,3,4,5,6,7,8,9,10}) { std::cout << i << " "; // i = 42; // error }
Aquí está la demostración en vivo
fuente
{..}
. Necesita incluir algo para activar esta función. Por ejemplo, su código se romperá si no agrega los encabezados adecuados: godbolt.org/z/esbhra . ¡Remitir<iostream>
para otros encabezados es una mala idea!#include <cstdio> #define protect(var) \ auto &var ## _ref = var; \ const auto &var = var ## _ref int main() { for (int i = 0; i < 10; ++i) { { protect(i); // do something with i // printf("%d\n", i); i = 42; // error!! remove this and it compiles. } } }
Nota: necesitamos anidar el alcance debido a una asombrosa estupidez en el lenguaje: la variable declarada en el
for(...)
encabezado se considera que está en el mismo nivel de anidación que las variables declaradas en la{...}
declaración compuesta. Esto significa que, por ejemplo:for (int i = ...) { int i = 42; // error: i redeclared in same scope }
¿Qué? ¿No acabamos de abrir un tirante rizado? Además, es inconsistente:
void fun(int i) { int i = 42; // OK }
fuente
Un enfoque simple aún no mencionado aquí que funciona en cualquier versión de C ++ es crear un contenedor funcional alrededor de un rango, similar a lo que
std::for_each
ocurre con los iteradores. El usuario es responsable de pasar un argumento funcional como una devolución de llamada que se invocará en cada iteración.Por ejemplo:
// A struct that holds the start and end value of the range struct numeric_range { int start; int end; // A simple function that wraps the 'for loop' and calls the function back template <typename Fn> void for_each(const Fn& fn) const { for (auto i = start; i < end; ++i) { const auto& const_i = i; fn(const_i); } } };
Dónde sería el uso:
numeric_range{0, 10}.for_each([](const auto& i){ std::cout << i << " "; // ok //i = 100; // error });
Cualquier cosa anterior a C ++ 11 se atascaría al pasar un puntero de función con nombre fuerte en
for_each
(similar astd::for_each
), pero aún funciona.Aquí hay una demostración
Aunque esto puede no ser idiomático para
for
bucles en C ++ , este enfoque es bastante común en otros lenguajes. Los envoltorios funcionales son realmente elegantes por su capacidad de composición en declaraciones complejas y pueden ser muy ergonómicos para su uso.Este código también es fácil de escribir, comprender y mantener.
fuente
[&]
O[=]
) para cumplir con ciertos estándares de seguridad, lo que puede inflar la lambda y cada miembro debe capturarse manualmente. No todas las organizaciones hacen esto, por lo que solo lo menciono como un comentario y no en la respuesta.template<class T = int, class F> void while_less(T n, F f, T start = 0){ for(; start < n; ++start) f(start); } int main() { int s = 0; while_less(10, [&](auto i){ s += i; }); assert(s == 45); }
tal vez llamarlo
for_i
Sin gastos generales https://godbolt.org/z/e7asGj
fuente