#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
。
有没有一种简单的方法可以使类似的结构起作用,即迭代不同变量中的值,例如此处的a
和b
。
它不起作用,因为{a,b}
您正在制作a
和b
的副本。一种可能的解决方案是使循环变量成为指针,取a
和b
的地址:
#include <initializer_list>
struct Obj {
int i;
};
Obj a, b;
int main() {
for(auto obj : {&a, &b}) {
obj->i = 123;
}
}
现场观看
注意:通常最好使用auto
,因为它可以避免静默隐式转换
这不起作用的原因是std::initializer_list
的底层元素是从a
和b
复制的,并且类型为const Obj
,所以你实际上是在尝试将常量值绑定到可变引用。
可以尝试使用以下方法解决此问题:
for (auto obj : {a, b}) {
obj.i = 123;
}
但很快会注意到对象a
和b
中i
的实际值没有变化。原因是当在这里使用auto
时,循环变量obj
的类型将变为Obj
,所以你只是循环a
和b
的副本。
应该解决此问题的实际方法是,您可以使用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在他的答案中所做的那样,但我有一个尽可能避免原始指针的习惯,在这种情况下,我只是相信使用引用使代码更清晰、更干净。
如果复制a
和b
是所需的行为,则可以使用临时数组而不是初始值设定项列表:
#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
是下一层概括。