Faixa inocente baseada em loop não funcionando

11

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 referência ao elemento diretamente quando o iterador é desassistido .

darune
fonte
11
Uma solução simples (que eu não iria realmente usar-me) é criar uma lista de ponteiros em vez disso: { &a, &b, &c, &d }.
Algum programador
2
initializer_listé principalmente uma visão da constmatriz.
Jarod42
O que eu provavelmente faria é inicializar explicitamente as variáveis, uma por uma. Não vai ser muito mais para escrever, é claro e explícito, e faz o que se pretende. :)
Algum programador
3
se você não quiser { &a, &b, &c, &d }, também não vai querer:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42 6/11/19
As perguntas "por que isso não é capaz de funcionar" é uma pergunta muito diferente de "o que posso fazer para que algo assim funcione?"
Nicol Bolas

Respostas:

4

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 uma 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;
}
mhhollomon
fonte
11
Você pode largar std::vector<int*>.
Jarod42
@mhhollomon afirmei explicitamente que não estou interessado em uma solução de indireção de ponteiro.
darune
11
Deveria ser auto sou auto* snão auto& s.
LF
@ Darune - Ficarei feliz em ter alguém para dar uma resposta diferente. Não está claro que tal resposta exista com o padrão atual.
Mhhollomon 6/11/19
@LF - concordou.
Mhhollomon 6/11/19
4

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
Evg
fonte
2
Esta é uma solução / solução alternativa decente e boa. Seu uma idéia semelhante a minha própria resposta (eu usei um std :: reference_wrapper e não usando o modelo variádica)
darune
4

De acordo com o padrão §11.6.4 Inicialização de lista / p5 [dcl.init.list] [ Mina de Ênfase ]:

Um objeto do tipo 'std :: initializer_list' é construído a partir de uma lista de inicializadores como se a implementação gerasse e materializasse (7.4) um pré-valor do tipo “array de N const E” , onde N é o número de elementos na lista de inicializadores. Cada elemento dessa matriz é inicializado por cópia com o elemento correspondente da lista de inicializadores, e o objeto std :: initializer_list é construído para se referir a essa matriz. [Nota: Um construtor ou uma função de conversão selecionada para a cópia deve estar acessível (Cláusula 14) no contexto da lista de inicializadores. - nota final] Se uma conversão de restrição for necessária para inicializar qualquer um dos elementos, o programa está incorreto.

Assim, seu compilador está reclamando legitimamente (isto é, auto &sdeduz int const& se você não pode atribuir a sno loop for variado).

Você pode aliviar esse problema introduzindo um contêiner em vez de uma lista de inicializadores (por exemplo, `std :: vector ') com' 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;
}

Demonstração ao vivo

101010
fonte
@ Jarod42 Ouups desculpe, alterou isso.
101010 06/11/19
Você solução não se encaixa meus critérios para uma boa solução - se eu estava feliz com o que eu não teria perguntado em primeiro lugar :)
darune
também a sua cotação não tenta responder a minha pergunta
darune
11
@ Darune - na verdade, a citação é a razão pela qual o seu for (auto& s : {a, b, c, d})não funciona. Quanto ao motivo pelo qual a norma possui essa cláusula ..... você teria que perguntar aos membros do comitê de padronização. Como muitas dessas coisas, o raciocínio pode ser qualquer coisa entre "Não consideramos o seu caso em particular útil o suficiente para nos preocuparmos" até "Muitas outras partes do padrão precisariam ser alteradas para apoiar o seu caso e adiamos a consideração de tudo isso até desenvolvermos um padrão futuro ".
Peter Peter
Você não pode simplesmente usar std::array<std::reference_wrapper>>?
precisa
1

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

Jarod42
fonte
11
Como isso difere std::reference_wrapper?
precisa
11
@TobySpeight: std::reference_wrapperexigiria s.get() = 1;.
Jarod42
0

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.

darune
fonte
-1

Você pode criar uma classe de wrapper para armazenar referência e qual terá o operador de atribuição para atualizar 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

Demonstração ao vivo

rafix07
fonte