如何迭代 C++ 中的非常量变量?
How to iterate over non-const variables in 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
。
是否有一种简单的方法可以使类似的构造起作用,即迭代不同变量中的值,例如此处的 a
和 b
。
这不起作用的原因是 std::initializer_list
的基础元素是从 a
和 b
复制而来的,并且类型为 const Obj
,所以你实际上是在尝试将一个常量值绑定到一个可变引用。
可以尝试使用以下方法解决此问题:
for (auto obj : {a, b}) {
obj.i = 123;
}
但很快就会注意到对象 a
和 b
中 i
的实际值没有改变。原因是这里使用 auto
时,循环变量 obj
的类型将变为 Obj
,所以你只是循环 a
和 [= 的副本16=].
解决此问题的实际方法是您可以使用 std::ref
(defined in the <functional>
header), to make the items in the initializer list be of type 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';
}
Output:
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}
中,您正在复制 a
和 b
。一种可能的解决方案是使循环变量成为指针,获取 a
和 b
:
的地址
#include <initializer_list>
struct Obj {
int i;
};
Obj a, b;
int main() {
for(auto obj : {&a, &b}) {
obj->i = 123;
}
}
看到了live
注意:通常使用 auto
更好,因为
如果复制 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
是泛化的下一层。
#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
。
这不起作用的原因是 std::initializer_list
的基础元素是从 a
和 b
复制而来的,并且类型为 const Obj
,所以你实际上是在尝试将一个常量值绑定到一个可变引用。
可以尝试使用以下方法解决此问题:
for (auto obj : {a, b}) {
obj.i = 123;
}
但很快就会注意到对象 a
和 b
中 i
的实际值没有改变。原因是这里使用 auto
时,循环变量 obj
的类型将变为 Obj
,所以你只是循环 a
和 [= 的副本16=].
解决此问题的实际方法是您可以使用 std::ref
(defined in the <functional>
header), to make the items in the initializer list be of type 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';
}
Output:
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}
中,您正在复制 a
和 b
。一种可能的解决方案是使循环变量成为指针,获取 a
和 b
:
#include <initializer_list>
struct Obj {
int i;
};
Obj a, b;
int main() {
for(auto obj : {&a, &b}) {
obj->i = 123;
}
}
看到了live
注意:通常使用 auto
更好,因为
如果复制 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
是泛化的下一层。