c++ initializer-list

Iterar sobre variables no constantes en C++



initializer-list (4)

La razón por la que eso no funciona es que los elementos subyacentes de std::initializer_list se copian de a y b , y son de tipo const Obj , por lo que esencialmente está tratando de vincular un valor constante a una referencia mutable.

Uno podría intentar solucionar esto usando:

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

pero pronto notaría que los valores reales de i en los objetos b no cambiaron. La razón es que cuando se usa auto aquí, el tipo de la variable de bucle obj se convertirá en Obj , por lo que simplemente está haciendo un bucle sobre las copias de b .

La forma real de solucionar esto es que puede usar std::ref (definido en el encabezado <functional> ), para que los elementos en la lista de inicializadores sean de tipo std::reference_wrapper<Obj> . Eso es implícitamente convertible a Obj& , por lo que puede mantenerlo como el tipo de la variable de bucle:

#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''; }

Salida:

123 123

Una forma alternativa de hacer lo anterior sería hacer que el bucle use const auto& y std::reference_wrapper<T>::get . Podemos usar una referencia constante aquí, porque reference_wrapper no se altera, solo el valor que envuelve sí:

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

pero creo que de esta manera usar auto y .get() es bastante engorroso y probablemente no sea preferible en la mayoría de los casos.

Puede parecer más simple hacer esto usando punteros sin procesar en el bucle como @francesco hizo en su respuesta, pero tengo la costumbre de evitar los punteros sin procesar tanto como sea posible, y en este caso solo creo que usar referencias hace El código más claro y más limpio.

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

Este código no se compila porque los valores de initializer_list {a, b} se toman como const Obj& , y no se pueden vincular a la referencia no const obj .

¿Hay una manera simple de hacer que una construcción similar funcione, es decir, iterar sobre valores que están en diferentes variables, como a y b aquí?


No funciona porque en {a,b} estás haciendo una copia de a y b . Una posible solución sería hacer que la variable de bucle sea un puntero, tomando las direcciones de a y b :

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

Míralo en live

Nota: genéricamente es mejor usar auto , ya que podría evitar conversiones implícitas silenciosas


Si copiar b es el comportamiento deseado, puede usar una matriz temporal en lugar de una lista de inicializador:

#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; } }


Un poco feo pero puedes hacer esto, ya que 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)); }

Incluso funcionaría si los objetos no son todos del mismo tipo, haciendo f un conjunto de sobrecarga. También puede hacer que f sea ​​una lambda local (posiblemente sobrecargada). Enlace a la inspiración .

El std::tie evita el problema en el código original porque genera una tupla de referencias. Desafortunadamente, std::begin no está definido para tuplas donde todos los miembros son del mismo tipo (¡eso sería bueno!), Por lo que no podemos usar el bucle for basado en rangos. Pero std::apply es la siguiente capa de generalización.