如何迭代 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

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

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

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

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

但很快就会注意到对象 abi 的实际值没有改变。原因是这里使用 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} 中,您正在复制 ab。一种可能的解决方案是使循环变量成为指针,获取 ab:

的地址
#include <initializer_list>

struct Obj {
    int i;
};

Obj a, b;

int main() {
    for(auto obj : {&a, &b}) {
        obj->i = 123;   
    }
}

看到了live

注意:通常使用 auto 更好,因为

如果复制 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 是泛化的下一层。