c++ for-loop c++17 initializer-list

c++ - Rango inocente basado en bucle que no funciona



for-loop c++17 (6)

Solución: use un contenedor de referencia

template <class It> struct range_view_iterator : public It{//TODO: don''t inherit It auto& operator*() { return (*this)->get(); } }; template<class It> range_view_iterator(It) -> range_view_iterator<It>; template<class T> struct range_view { std::vector<std::reference_wrapper<T> > refs_; range_view(std::initializer_list<std::reference_wrapper<T> > refs) : refs_{refs} { } auto begin() { return range_view_iterator{ refs_.begin() }; } auto end() { return range_view_iterator{ refs_.end() }; } };

Luego se usa como:

for (auto& e : range_view<int>{a, b, c, d}) { e = 1; }

Sin embargo, esto no intenta responder a la primera pregunta.

Lo siguiente no compila:

#include <iostream> int main() { int a{},b{},c{},d{}; for (auto& s : {a, b, c, d}) { s = 1; } std::cout << a << std::endl; return 0; }

Pruébalo en godbolt

El error del compilador es: error: assignment of read-only reference ''s''

Ahora, en mi caso real, la lista está hecha de variables miembro en una clase.

Ahora, esto no funciona porque la expresión se convierte en initializer_list<int> que realmente copia a, b, c y d, por lo tanto, tampoco permite modificaciones.

Mi pregunta es doble:

¿Hay alguna motivación detrás de no permitir escribir un bucle for basado en rango de esta manera? p.ej. tal vez podría haber un caso especial para las expresiones con llaves desnudas.

¿Cuál es una forma ordenada sintáctica de arreglar este tipo de bucle?

Algo en esta línea sería preferible:

for (auto& s : something(a, b, c, d)) { s = 1; }

No considero que la indirección del puntero sea una buena solución (es decir, {&a, &b, &c, &d} ): cualquier solución debe dar referencia al elemento directamente cuando se des-referencia el iterador .


De acuerdo con el estándar §11.6.4 List-initialization / p5 [dcl.init.list] [ Emphasis Mine ]:

Un objeto de tipo ''std :: initializer_list'' se construye a partir de una lista de inicializadores como si la implementación generara y materializara (7.4) un valor de tipo “matriz de N const E” , donde N es el número de elementos en la lista de inicializadores. Cada elemento de esa matriz se inicializa con el elemento correspondiente de la lista de inicializadores, y el objeto std :: initializer_list se construye para referirse a esa matriz. [Nota: Un constructor o función de conversión seleccionada para la copia estará accesible (Cláusula 14) en el contexto de la lista de inicializadores. - nota final] Si se requiere una conversión de reducción para inicializar cualquiera de los elementos, el programa está mal formado.

Por lo tanto, su compilador se queja legítimamente (es decir, auto &s deduce a int const& s no puede asignar a s en el rango for loop).

Puede aliviar este problema introduciendo un contenedor en lugar de una lista de inicializador (por ejemplo, `std :: vector '') con'' std :: reference_wrapper '':

#include <iostream> #include <vector> #include <functional> int main() { int a{},b{},c{},d{}; for (auto& s : std::vector<std::reference_wrapper<int>>{a, b, c, d}) { s.get()= 1; } std::cout << a << std::endl; return 0; }

Demo en vivo


Los rangos no son tan mágicos como a la gente le gustaría. Al final, debe haber un objeto para que el compilador pueda generar llamadas a una función miembro o a una función libre begin() y end() .

Lo más probable que puedas venir es:

#include <iostream> int main() { int a{},b{},c{},d{}; for (auto s : {&a, &b, &c, &d} ) { *s = 1; } std::cout << a << "/n"; return 0; }


Para satisfacer esa sintaxis

for (auto& s : something{a, b, c, d}) { s = 1; }

podrías crear un contenedor:

template <typename T> struct MyRefWrapper { public: MyRefWrapper(T& p) : p(&p) {} T& operator =(const T& value) const { return *p = value; } operator T& () const { return *p; } private: T* p; };

Demo


Puede crear una clase de contenedor para almacenar referencias y que tendrá un operador de asignación para actualizar este valor:

template<class T> struct Wrapper { T& ref; Wrapper(T& ref) : ref(ref){} template<class U> void operator=(U u) { ref = u; } }; template<class...T> auto sth(T&...t) { return std::array< Wrapper<std::common_type_t<T...> > ,sizeof...(t) >{Wrapper(t)...}; }; int main(){ int a{},b{},c{},d{}; for (auto s : sth(a,b,c,d)) { s = 1; } std::cout << a << std::endl; // 1

Demo en vivo


Solo otra solución dentro de una idea envolvente:

template<typename T, std::size_t size> class Ref_array { using Array = std::array<T*, size>; class Iterator { public: explicit Iterator(typename Array::iterator it) : it_(it) {} void operator++() { ++it_; } bool operator!=(const Iterator& other) const { return it_ != other.it_; } decltype(auto) operator*() const { return **it_; } private: typename Array::iterator it_; }; public: explicit Ref_array(Array args) : args_(args) {} auto begin() { return Iterator(args_.begin()); } auto end() { return Iterator(args_.end()); } private: Array args_; }; template<typename T, typename... Ts> auto something(T& first, Ts&... rest) { static_assert((std::is_same_v<T, Ts> && ...)); return Ref_array<T, 1 + sizeof...(Ts)>({&first, &rest...}); }

Luego:

int main() { int a{}, b{}, c{}, d{}; for (auto& s : something(a, b, c, d)) { std::cout << s; s = 1; } std::cout << std::endl; for (auto& s : something(a, b, c, d)) std::cout << s; }

salidas

0000 1111