如何在C++中迭代非常量变量?


#include <initializer_list>
struct Obj {
int i;
};
Obj a, b;
int main() {
for(Obj& obj : {a, b}) {
obj.i = 123;   
}
}

此代码不编译,因为initializer_list{a, b}中的值被视为const Obj&,并且不能绑定到非常量引用obj

有没有一种简单的方法可以使类似的结构起作用,即迭代不同变量中的值,例如此处的ab

它不起作用,因为{a,b}您正在制作ab的副本。一种可能的解决方案是使循环变量成为指针,取ab的地址:

#include <initializer_list>
struct Obj {
int i;
};
Obj a, b;
int main() {
for(auto obj : {&a, &b}) {
obj->i = 123;   
}
}

现场观看

注意:通常最好使用auto,因为它可以避免静默隐式转换

这不起作用的原因是std::initializer_list的底层元素是从ab复制的,并且类型为const Obj,所以你实际上是在尝试将常量值绑定到可变引用。

可以尝试使用以下方法解决此问题:

for (auto obj : {a, b}) {
obj.i = 123;
}

但很快会注意到对象abi的实际值没有变化。原因是当在这里使用auto时,循环变量obj的类型将变为Obj,所以你只是循环ab的副本。

应该解决此问题的实际方法是,您可以使用std::ref(在<functional>标头中定义)使初始值设定项列表中的项类型为std::reference_wrapper<Obj>。这是隐式转换为Obj&,因此您可以将其保留为循环变量的类型:

#include <functional>
#include <initializer_list>
#include <iostream>
struct Obj {
int i;
};
Obj a, b;
int main()
{
for (Obj& obj : {std::ref(a), std::ref(b)}) { 
obj.i = 123;
}
std::cout << a.i << 'n';
std::cout << b.i << 'n';
}

输出:

123
123

执行上述操作的另一种方法是使循环使用const auto&std::reference_wrapper<T>::get。我们可以在这里使用常量引用,因为reference_wrapper不会被改变,只是它包装的值会改变:

for (const auto& obj : {std::ref(a), std::ref(b)}) { 
obj.get().i = 123;
}

但我认为,因为在这里使用auto强制使用.get(),这是非常麻烦的,前一种方法是解决此问题的优选方法。


通过在循环中使用原始指针来做到这一点似乎更简单,就像@francesco在他的答案中所做的那样,但我有一个尽可能避免原始指针的习惯,在这种情况下,我只是相信使用引用使代码更清晰、更干净。

如果复制ab是所需的行为,则可以使用临时数组而不是初始值设定项列表:

#include <initializer_list>
struct Obj {
int i;
} a, b;
int main() {
typedef Obj obj_arr[];
for(auto &obj : obj_arr{a, b}) {
obj.i = 123;   
}
}

即使Obj只有一个移动构造函数,这也有效。

有点丑,但你可以这样做,从 C++17 开始:

#define APPLY_TUPLE(func, t) std::apply( [](auto&&... e) { ( func(e), ...); }, (t))
static void f(Obj& x)
{
x.i = 123;
}
int main() 
{
APPLY_TUPLE(f, std::tie(a, b));
}

如果对象不是所有相同类型,它甚至可以通过使f成为重载集来工作。 你也可以让f是一个(可能重载的)本地 lambda 。 链接到灵感。

std::tie

避免了原始代码中的问题,因为它生成了一个引用元组。 不幸的是,std::begin没有为所有成员都是相同类型的元组定义(那就太好了!),所以我们不能使用基于范围的 for 循环。但std::apply是下一层概括。

最新更新