#include <initializer_list>
struct Obj {
int i;
};
Obj a, b;
int main() {
for(Obj& obj : {a, b}) {
obj.i = 123;
}
}
This code does not compile because the values from the initializer_list
{a, b}
are taken as const Obj&
, and cannot be bound to the non-const reference obj
.
Is there a simple way to make a similar construct work, i.e. iterate over values that are in different variables, like a
and b
here.
It does not work because in {a,b}
you are making a copy of a
and b
. One possible solution would be to make the loop variable a pointer, taking the addresses of a
and b
:
#include <initializer_list>
struct Obj {
int i;
};
Obj a, b;
int main() {
for(auto obj : {&a, &b}) {
obj->i = 123;
}
}
See it live
Note: it is generically better to use auto
, as it could avoid silent implicit conversions
The reason why that doesn't work is that the underlying elements of the std::initializer_list
are copied from a
and b
, and are of type const Obj
, so you are essentially trying to bind a constant value to a mutable reference.
One could try to fix this by using:
for (auto obj : {a, b}) {
obj.i = 123;
}
but then would soon notice that the actual values of i
in the objects a
and b
didn't change. The reason is that when using auto
here, the type of the loop variable obj
will become Obj
, so then you're just looping over copies of a
and b
.
The actual way this should be fixed is that you can use std::ref
(defined in the <functional>
header), to make the items in the initializer list be of type std::reference_wrapper<Obj>
. That is implictly convertible to Obj&
, so you can keep that as the type of the loop variable:
#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
An alternative way to do the above would be to make the loop use const auto&
and std::reference_wrapper<T>::get
. We can use a constant reference here, because the reference_wrapper
doesn't get altered, just the value it wraps does:
for (const auto& obj : {std::ref(a), std::ref(b)}) {
obj.get().i = 123;
}
but I think that, because using auto
here forces the use of .get()
, this is quite cumbersome and the former method is the preferable way to solve this.
It might seem to be more simple to do this by using raw pointers in the loop as @francesco did in his answer, but I have a habit of avoiding raw pointers as much as possible, and in this case I just believe that using references makes the code clearer and cleaner.
If copying a
and b
is the desired behavior, you can use a temporary array rather than an initializer list:
#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;
}
}
This works even if Obj
only has a move constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With