Рассмотрим стандартный цикл for:
for (int i = 0; i < 10; ++i)
{
// do something with i
}
Я хочу предотвратить изменение переменной i
в теле for
цикла.
Тем не менее, я не могу объявить , i
как const
как это делает приращение заявление недействительным. Есть ли способ , чтобы сделать i
с const
переменным вне заявления приращения?
const int i
аргументом. Изменчивость индекса отображается только там, где это необходимо, и вы можете использоватьinline
ключевое слово, чтобы оно не влияло на скомпилированный вывод.const
для начала.Ответы:
Начиная с C ++ 20, вы можете использовать range :: views :: iota следующим образом:
for (int const i : std::views::iota(0, 10)) { std::cout << i << " "; // ok i = 42; // error }
Вот демо .
Начиная с C ++ 11, вы также можете использовать следующую технику, в которой используется IIILE (немедленно вызываемое встроенное лямбда-выражение):
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
Вот демо .
Обратите внимание, что это
[&,i]
означает, чтоi
это фиксируется неизменяемой копией, а все остальное фиксируется изменяемой ссылкой. Знак();
в конце цикла просто означает, что лямбда вызывается немедленно.источник
&
захват, который заставит явно захватить каждую ссылку - что делает это довольно громоздкий. Я также подозреваю, что это может привести к легким ошибкам, когда автор забывает()
, что делает код никогда не вызываемым. Это достаточно мало, чтобы не заметить его при проверке кода.[&]
поскольку они конфликтуют со стандартами кодирования, такими как AUTOSAR (Правило A5-1-2), HIC ++ и, я думаю, также MISRA (не уверен). Это не значит, что это неправильно; Дело в том, что организации запрещают этот тип кода, чтобы он соответствовал стандартам. Что касается()
, новейшая версия gcc не отмечает это даже с-Wextra
. Я по-прежнему считаю подход изящным; это просто не работает для многих организаций.Для тех, кому нравится
std::views::iota
ответ Cigien, но не работает на C ++ 20 или выше, довольно просто реализовать упрощенную и облегченную версиюstd::views::iota
совместимогоc ++ 11 или выше.Все, что для этого требуется:
operator++
иoperator*
), который содержит целое значение (например,int
)begin()
иend()
возвращает вышеуказанные итераторы. Это позволит ему работать вfor
циклах на основе диапазонаУпрощенная версия этого может быть:
#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}; }
Я определил выше,
constexpr
где это поддерживается, но для более ранних версий C ++, таких как C ++ 11/14, вам может потребоваться удалить,constexpr
если это не разрешено в этих версиях.Приведенный выше шаблон позволяет следующему коду работать в версиях до C ++ 20:
for (int const i : iota(0, 10)) { std::cout << i << " "; // ok i = 42; // error }
Что будет генерировать ту же сборку, что и решение C ++ 20,
std::views::iota
и классическоеfor
решение -loop при оптимизации.Это работает с любыми компиляторами, совместимыми с C ++ 11 (например, с подобными компиляторами
gcc-4.9.4
), и по-прежнему производит почти идентичную сборку с базовымfor
аналогом -loop.Примечание .
iota
Вспомогательная функция предназначена только для обеспечения паритета функций сstd::views::iota
решением C ++ 20 ; но на самом деле вы также можете напрямую построитьiota_range{...}
вместо вызоваiota(...)
. Первый вариант представляет собой простой способ обновления, если пользователь желает в будущем перейти на C ++ 20.источник
int
, а затем создание класса «диапазона» для возврата начала / концаВерсия KISS ...
for (int _i = 0; _i < 10; ++_i) { const int i = _i; // use i here }
Если ваш вариант использования предназначен только для предотвращения случайного изменения индекса цикла, это должно сделать такую ошибку очевидной. (Если вы хотите предотвратить преднамеренное изменение, что ж, удачи ...)
источник
_
. И небольшое пояснение (например, объем) было бы полезно. В остальном да, хорошо KISSy.i_
было бы более приемлемым._i
прежнему может быть изменена в цикле.std::views::iota
для полностью пуленепробиваемого способа. Текст ответа объясняет его ограничения и то, как он пытается ответить на вопрос. Куча чрезмерно усложненного C ++ 11 делает лекарство хуже, чем болезнь, с точки зрения легкого для чтения и обслуживания IMO. Это все еще очень легко читать для всех, кто знает C ++, и кажется разумным в качестве идиомы. (Но следует избегать имен, начинающихся с подчеркивания.)_Uppercase
иdouble__underscore
идентификаторы зарезервированы._lowercase
идентификаторы зарезервированы только в глобальной области.Не могли бы вы просто переместить часть или все содержимое цикла for в функцию, которая принимает i как константу?
Это менее оптимально, чем некоторые предлагаемые решения, но, если возможно, сделать это довольно просто.
Изменить: просто пример, поскольку я обычно не понимаю.
for (int i = 0; i < 10; ++i) { looper( i ); } void looper ( const int v ) { // do your thing here }
источник
Если у вас нет доступа к c ++ 20, типичный макияж с использованием функции
#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; }
теперь ты мог
for (const int i : makeRange(0, 10)) { std::cout << i << " "; // ok //i = 100; // error }
( См. Демонстрацию )
Обновление : Вдохновленный комментарием @ Human-Compiler , мне было интересно, будут ли данные ответы иметь какое-либо различие в случае производительности. Оказалось, что, за исключением этого подхода, все остальные подходы на удивление имеют одинаковую производительность (для диапазона
[0, 10)
).std::vector
Подход является наихудшим.( См. Интерактивную быструю скамью )
источник
vector
. Если диапазон очень большой, это может быть плохо.std::vector
довольно ужасен в относительном масштабе, если диапазон тоже невелик, и мог бы быть очень плохим, если бы предполагалось, что это будет небольшой внутренний цикл, который выполняется много раз. Некоторые компиляторы (например, clang с libc ++, но не с libstdc ++) могут оптимизировать новое / удаление выделения, которое не выходит за пределы функции, но в противном случае это может легко быть разницей между небольшим полностью развернутым циклом и вызовомnew
+delete
, и, возможно, действительно хранится в этой памяти.const i
просто не стоит накладных расходов в большинстве случаев без С ++ 20 способов, которые делают его дешевым. Особенно с диапазонами переменных времени выполнения, которые уменьшают вероятность того, что компилятор все оптимизирует.А вот версия C ++ 11:
for (int const i : {0,1,2,3,4,5,6,7,8,9,10}) { std::cout << i << " "; // i = 42; // error }
Вот живая демонстрация
источник
{..}
. Вам нужно что-то добавить, чтобы эта функция стала активной. Например, ваш код сломается, если вы не добавите правильные заголовки: godbolt.org/z/esbhra . Ретрансляция<iostream>
для других заголовков - плохая идея!#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. } } }
Примечание: нам нужно вложить область видимости из-за поразительной глупости языка: переменная, объявленная в
for(...)
заголовке, считается находящейся на том же уровне вложенности, что и переменные, объявленные в{...}
составном операторе. Это означает, например, что:for (int i = ...) { int i = 42; // error: i redeclared in same scope }
Какая? Разве мы не открыли фигурную скобку? Более того, это непоследовательно:
void fun(int i) { int i = 42; // OK }
источник
Еще не упомянутый здесь простой подход, который работает в любой версии C ++, - это создание функциональной оболочки вокруг диапазона, аналогичной той, что
std::for_each
происходит с итераторами. Затем пользователь отвечает за передачу функционального аргумента в качестве обратного вызова, который будет вызываться на каждой итерации.Например:
// 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); } } };
Где использовать:
numeric_range{0, 10}.for_each([](const auto& i){ std::cout << i << " "; // ok //i = 100; // error });
Все, что старше C ++ 11, застряло бы при передаче указателя на функцию со строгим именем в
for_each
(аналогичноstd::for_each
), но это все еще работает.Вот демо
Хотя это может быть идиоматическим для
for
циклов в C ++ , этот подход довольно распространен в других языках. Функциональные оболочки действительно изящны из-за их возможности компоновки сложных операторов и могут быть очень эргономичными для использования.Этот код также легко писать, понимать и поддерживать.
источник
[&]
или[=]
) в соответствии с определенными стандартами безопасности, что может привести к раздуванию лямбда-выражения, когда каждый член должен быть захвачен вручную. Не все организации делают это, поэтому я упоминаю это только как комментарий, а не как ответ.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); }
может называть это
for_i
Без накладных расходов https://godbolt.org/z/e7asGj
источник