考虑一个标准的for循环:
for (int i = 0; i < 10; ++i)
{
// do something with i
}
我想防止变量i
在for
循环体中被修改。
但是,我无法声明i
,const
因为这会使增量语句无效。有没有一种方法,使i
一个const
增量声明的变量外?
考虑一个标准的for循环:
for (int i = 0; i < 10; ++i)
{
// do something with i
}
我想防止变量i
在for
循环体中被修改。
但是,我无法声明i
,const
因为这会使增量语句无效。有没有一种方法,使i
一个const
增量声明的变量外?
const int i
参数的函数。索引的可变性仅在需要的地方公开,您可以使用inline
关键字使其对编译后的输出不起作用。
const
开始的原因。
Answers:
从c ++ 20开始,您可以像这样使用range :: views :: iota:
for (int const i : std::views::iota(0, 10))
{
std::cout << i << " "; // ok
i = 42; // error
}
这是一个演示。
从c ++ 11开始,您还可以使用以下技术,该技术使用IIILE(立即调用的内联lambda表达式):
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
是由非可变副本捕获的,其他所有内容都是由可变引用捕获的。该();
在循环的结束只是意味着该拉姆达立即调用。
&
捕获,这将强制显式捕获每个引用-这使得它相当麻烦的 我还怀疑这可能导致容易产生的错误,导致作者忘记了()
,从而使代码永不被调用。这很容易小到足以在代码审查中丢失。
对于喜欢Cigien的std::views::iota
答案但不能在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 ++ 11/14等早期版本的C ++,您可能需要删除constexpr
那些版本中不合法的位置。
上面的样板使以下代码可以在C ++ 20之前的版本中工作:
for (int const i : iota(0, 10))
{
std::cout << i << " "; // ok
i = 42; // error
}
优化后,它将生成与C ++ 20std::views::iota
解决方案和经典for
-loop解决方案相同的程序集。
这适用于任何符合C ++ 11的编译器(例如gcc-4.9.4
),并且仍会产生与基本for
-loop副本几乎相同的汇编。
注意:该iota
辅助函数仅仅是特征奇偶与C ++ 20std::views::iota
溶液; 但实际上,您也可以直接构造一个iota_range{...}
而不是调用iota(...)
。如果用户将来希望切换到C ++ 20,则前者只是一个简单的升级途径。
int
,然后创建一个“范围”类以返回开始/结束
吻版本...
for (int _i = 0; _i < 10; ++_i) {
const int i = _i;
// use i here
}
如果您的用例只是为了防止意外修改循环索引,则应该使此类错误显而易见。(如果您想防止故意修改,祝您好运...)
_
。并作一些解释(例如范围)会有所帮助。否则,是的,很好。
i_
会更合规。
_i
仍可在循环中修改的变量。
std::views::iota
以完全防弹的方式使用,那么此局部解决方案是值得的。答案的文字说明了其局限性以及它如何尝试回答问题。从易于阅读,易于维护的IMO角度来看,一堆过于复杂的C ++ 11使这种疗法比疾病更糟糕。对于每个了解C ++的人来说,这仍然很容易阅读,并且作为一种习惯用法似乎很合理。(但应避免使用下划线开头的名称。)
_Uppercase
,double__underscore
标识符保留。_lowercase
标识符仅在全局范围内保留。
如果您无权访问 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的评论中得到启发,我想知道天气给出的答案在性能方面有什么不同。事实证明,除了这种方法之外,所有其他方法都具有令人惊讶的相同性能(针对range [0, 10)
)。这种std::vector
方法是最糟糕的。
vector
。如果范围很大,可能会很糟糕。
std::vector
如果范围很小,A在相对规模上也是相当糟糕的,如果这应该是运行了很多次的小内循环,则可能会非常糟糕。一些编译器(例如带有libc ++的clang,但不是libstdc ++)可以优化无法逃避该功能的分配的新/删除,但是,否则,这很容易成为小的完全展开循环与对new
+的调用之间的区别delete
,并且实际上可能存储在该内存中。
const i
如果没有C ++ 20便宜的方法,在大多数情况下,次要的好处就是不值得开销。尤其是使用运行时变量范围,这使编译器不太可能优化所有内容。
这是C ++ 11版本:
for (int const i : {0,1,2,3,4,5,6,7,8,9,10})
{
std::cout << i << " ";
// i = 42; // error
}
#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
),但仍会起作用。
这是一个演示
尽管这对于C ++中的for
循环可能不是惯用的,但是这种方法在其他语言中非常普遍。功能性包装程序在复杂语句中的组合性确实非常时尚,使用起来非常符合人体工程学。
该代码也易于编写,理解和维护。
[&]
或[=]
)上的默认捕获以符合某些安全标准,这可能会使lambda膨胀,而每个成员都需要手动捕获。并非所有组织都这样做,因此我仅将其作为评论而不是答案提及。
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