c++ - Faixa inocente baseada em loop não funcionando




for-loop c++17 (4)

Solução: use um wrapper de referência

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

Então usado como:

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

Isso não tenta responder à primeira pergunta.

O seguinte não 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;
}

Experimente no godbolt

O erro do compilador é: error: assignment of read-only reference 's'

Agora, no meu caso atual, a lista é feita de variáveis ​​de membro em uma classe.

Agora, isso não funciona porque a expressão se torna um initializer_list<int> que realmente copia a, b, ce ed - portanto, também não permite modificações.

Minha pergunta é dupla:

Existe alguma motivação por trás de não permitir escrever um loop for baseado em intervalo dessa maneira? por exemplo. talvez possa haver um caso especial para expressões de chaves nuas.

Qual é uma maneira pura e sintática de corrigir esse tipo de loop?

Algo nessa linha seria preferido:

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

Não considero a indireção do ponteiro uma boa solução (ou seja, {&a, &b, &c, &d} ) - qualquer solução deve fornecer a referência do elemento diretamente quando o iterador é desassistido .


Apenas outra solução dentro de uma ideia de wrapper:

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

Então:

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

saídas

0000
1111

Os intervalos não são tão mágicos quanto as pessoas gostariam. No final, deve haver um objeto no qual o compilador possa gerar chamadas para uma função membro ou para a função livre begin() e end() .

O mais próximo que você provavelmente poderá chegar é:

#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 satisfazer essa sintaxe

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

você pode criar wrapper:

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





initializer-list