问题
Consider a standard for loop:
for (int i = 0; i < 10; ++i)
{
// do something with i
}
I want to prevent the variable i
from being modified in the body of the for
loop.
However, I cannot declare i
as const
as this makes the increment statement invalid. Is there a way to make i
a const
variable outside of the increment statement?
回答1:
From c++20, you can use ranges::views::iota like this:
for (int const i : std::views::iota(0, 10))
{
std::cout << i << " "; // ok
i = 42; // error
}
Here's a demo.
From c++11, you can also use the following technique, which uses an IIILE (immediately invoked inline lambda expression):
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
Here's a demo.
Note that [&,i]
means that i
is captured by non-mutable copy, and everything else is captured by mutable reference. The ();
at the end of the loop simply means that the lambda is invoked immediately.
回答2:
For anyone that likes Cigien's std::views::iota
answer but isn't working in C++20 or above, it's rather straightforward to implement a simplified and lightweight version of std::views::iota
compatible c++11 or above.
All it requires is:
- A basic "LegacyInputIterator" type (something that defines
operator++
andoperator*
) that wraps an integral value (e.g. anint
) - Some "range"-like class that has
begin()
andend()
that returns the above iterators. This will allow it to work in range-basedfor
loops
A simplified version of this could be:
#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};
}
I've defined the above with constexpr
where it's supported, but for earlier versions of C++ like C++11/14, you may need to remove constexpr
where it is not legal in those versions to do so.
The above boilerplate enables the following code to work in pre-C++20:
for (int const i : iota(0, 10))
{
std::cout << i << " "; // ok
i = 42; // error
}
Which will generate the same assembly as the C++20 std::views::iota
solution and the classic for
-loop solution when optimized.
This works with any C++11-compliant compilers (e.g. compilers like gcc-4.9.4
) and still produces nearly identical assemblies to a basic for
-loop counterpart.
Note: The iota
helper function is just for feature-parity with the C++20 std::views::iota
solution; but realistically, you could also directly construct an iota_range{...}
instead of calling iota(...)
. The former just presents an easy upgrade path if a user wishes to switch to C++20 in the future.
回答3:
The KISS version...
for (int _i = 0; _i < 10; ++_i) {
const int i = _i;
// use i here
}
If your use case is just to prevent accidental modification of the loop index then this should make such a bug obvious. (If you want to prevent intentional modification, well, good luck...)
回答4:
If you do not have access to c++20, typical makeover using a function
#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;
}
now you could
for (const int i : makeRange(0, 10))
{
std::cout << i << " "; // ok
//i = 100; // error
}
(See a Demo)
Update: Inspired from the @Human-Compiler's comment, I was wondering weather the given answers have any difference in the case of performance. It turn out that, except for this approach, for all other approaches surprisingly have same performance (for the range [0, 10)
). The std::vector
approach is the worst.
(See Online Quick-Bench)
回答5:
Couldn't you just move some or all the content of your for loop in a function that accepts i as a const?
Its less optimal than some solutions proposed, but if possible this is quite simple to do.
Edit: Just an example as I tend to be unclear.
for (int i = 0; i < 10; ++i)
{
looper( i );
}
void looper ( const int v )
{
// do your thing here
}
回答6:
And here is a C++11 version:
for (int const i : {0,1,2,3,4,5,6,7,8,9,10})
{
std::cout << i << " ";
// i = 42; // error
}
Here is live demo
回答7:
#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.
}
}
}
Note: we need to nest the scope because of an astonishing stupidity in the language: the variable declared in the for(...)
header is considered to be at the same nesting level as variables declared in the {...}
compound statement. This means that, for instance:
for (int i = ...)
{
int i = 42; // error: i redeclared in same scope
}
What? Didn't we just open a curly brace? Moreover, it's inconsistent:
void fun(int i)
{
int i = 42; // OK
}
来源:https://stackoverflow.com/questions/63399816/how-to-make-a-for-loop-variable-const-with-the-exception-of-the-increment-statem