Considere un estándar para bucle:
for (int i = 0; i < 10; ++i)
{
// do something with i
}
Quiero evitar que la variable ise modifique en el cuerpo del forbucle.
Sin embargo, no puedo declararlo iya constque esto invalida la declaración de incremento. ¿Hay alguna forma de hacer iuna constvariable fuera de la declaración de incremento?

const int iargumento. La mutabilidad del índice solo se expone donde es necesario y puede usar lainlinepalabra clave para que no tenga ningún efecto en la salida compilada.constpara 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 }(); // IIILEAquí tienes una demostración .
Tenga en cuenta que eso
[&,i]significa queise 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::iotarespuesta de Cigien pero no esté trabajando en C ++ 20 o superior, es bastante sencillo implementar una versión simplificada y liviana destd::views::iotacompatiblec ++ 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 enforbucles 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
constexprdónde es compatible, pero para versiones anteriores de C ++ como C ++ 11/14, es posible que deba eliminarloconstexprdonde 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::iotasolución C ++ 20 y laforsolució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 unforequivalente básico de bucle.Nota: La
iotafunción auxiliar es solo para la paridad de características con lastd::views::iotasolució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
inty 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._ique todavía se puede modificar en el bucle.std::views::iotade 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.)_Uppercasey losdouble__underscoreidentificadores están reservados._lowercaselos 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::vectorenfoque es el peor.( Consulte Quick-Bench en línea )
fuente
vector. Si el rango es muy grande, esto podría ser malo.std::vectores 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 isimplemente 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_eachocurre 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
forbucles 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_iSin gastos generales https://godbolt.org/z/e7asGj
fuente