Rango inocente basado en bucle que no funciona

11

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 una 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 .

Darune
fuente
1
Una solución sencilla (que no iba a utilizar realmente a mí mismo) es crear una lista de punteros en su lugar: { &a, &b, &c, &d }.
Algún tipo programador el
2
initializer_listes principalmente una vista en constmatriz.
Jarod42
Lo que probablemente haría es inicializar explícitamente las variables, una por una. No va a ser mucho más escribir, es claro y explícito, y hace lo que se pretende. :)
Algún programador amigo el
3
si no quieres { &a, &b, &c, &d }, tampoco querrás:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42
La pregunta "¿por qué no puede funcionar esto?" Es una pregunta muy diferente de "¿qué puedo hacer para que algo como esto funcione?"
Nicol Bolas

Respuestas:

4

Los rangos no son tan mágicos como a la gente le gustaría. Al final, debe haber un objeto que el compilador pueda generar llamadas a una función miembro o 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;
}
mhhollomon
fuente
1
Puedes caer std::vector<int*>.
Jarod42
@mhhollomon Dije explícitamente que no estoy interesado en una solución de indirección de puntero.
Darune
1
Debería ser auto so auto* sno auto& s.
LF
@darune: estaré feliz de que alguien dé una respuesta diferente. No está claro que tal respuesta exista con el estándar actual.
mhhollomon
@LF - de acuerdo.
mhhollomon
4

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...});
}

Entonces:

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
Evg
fuente
2
Esta es una solución / solución decente y buena. Es una idea similar a mi propia respuesta (usé un std :: reference_wrapper y no uso una plantilla variadic)
darune
4

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 &sdeduce int const& sy no puede asignarlo sen el bucle a distancia).

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

101010
fuente
@ Jarod42 Ouups lo siento, modificó eso.
101010
Su solución no se ajusta a mis criterios para una buena solución; si estuviera contento con eso, no habría preguntado en primer lugar :)
Darune
también su cita no intenta responder a mi pregunta
darune
1
@darune: en realidad, la cita es la razón por la que tu for (auto& s : {a, b, c, d})no funciona. En cuanto a por qué el estándar tiene esa cláusula ..... tendrías que preguntar a los miembros del comité de estandarización. Al igual que muchas de estas cosas, el razonamiento podría ser cualquier cosa entre "No consideramos que su caso en particular fuera lo suficientemente útil como para preocuparse por" hasta "Demasiadas otras partes del estándar tendrían que cambiar para respaldar su caso, y pospusimos la consideración de todo eso hasta que desarrollemos un estándar futuro ".
Peter
¿No puedes usarlo std::array<std::reference_wrapper>>?
Toby Speight el
1

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

Manifestación

Jarod42
fuente
1
¿En qué difiere eso std::reference_wrapper?
Toby Speight el
1
@TobySpeight: std::reference_wrapperrequeriría s.get() = 1;.
Jarod42
0

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.

Darune
fuente
-1

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

rafix07
fuente