Gamma innocente basata su loop non funzionante


11

Non compila quanto segue :

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

Provalo su godbolt

L'errore del compilatore è: error: assignment of read-only reference 's'

Ora nel mio caso reale l'elenco è composto da variabili membro su una classe.

Ora, questo non funziona perché l'espressione diventa a initializer_list<int>che copia effettivamente a, b, c e d - quindi non consente nemmeno la modifica.

La mia domanda è duplice:

C'è qualche motivazione dietro a non permettere di scrivere un loop basato su range in questo modo? per esempio. forse potrebbe esserci un caso speciale per le espressioni di parentesi graffe nude.

Qual è un modo sintattico per sistemare questo tipo di loop?

Qualcosa su questa linea sarebbe preferito:

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

Non considero l'indirizzamento indiretto del puntatore una buona soluzione (cioè {&a, &b, &c, &d}) - qualsiasi soluzione dovrebbe fornire direttamente l'elemento di riferimento quando l'iteratore viene de-referenziato .


1
Una soluzione semplice (che non avrei davvero usare me stesso) è quello di creare una lista di puntatori invece: { &a, &b, &c, &d }.
Qualche programmatore, amico,

2
initializer_listè principalmente una vista constsull'array.
Jarod42

Quello che probabilmente farei è inizializzare esplicitamente le variabili, una per una. Non sarà molto più da scrivere, è chiaro ed esplicito e fa quello che è previsto. :)
Qualche programmatore amico

3
se non vuoi { &a, &b, &c, &d }, non vorrai nemmeno:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

Le domande "perché questo non è in grado di funzionare" è una domanda molto diversa da "cosa posso fare per far funzionare qualcosa del genere?"
Nicol Bolas,

Risposte:


4

Gli intervalli non sono così magici come vorrebbe la gente. Alla fine, ci deve essere un oggetto che il compilatore può generare chiamate a una funzione membro o libera begin()e end().

Il più vicino che probabilmente sarai in grado di venire è:

#include <iostream>

int main()
{
    int a{},b{},c{},d{};

    for (auto s : {&a, &b, &c, &d} ) {
        *s = 1;
    }
    std::cout << a << "\n";
    return 0;
}

1
Puoi lasciar perdere std::vector<int*>.
Jarod42,

@mhhollomon Ho dichiarato esplicitamente di non essere interessato a una soluzione indiretta del puntatore.
martedì

1
Dovrebbe essere auto so auto* sno auto& s.
LF

@darune - Sarò felice di avere qualcuno che dia una risposta diversa. Non è chiaro che tale risposta esiste con lo standard attuale.
mhhollomon,

@LF - concordato.
mhhollomon,

4

Solo un'altra soluzione all'interno di un'idea 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...});
}

Poi:

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

uscite

0000
1111

2
Questa è una soluzione / soluzione decente e buona. È un'idea simile alla mia stessa risposta (ho usato uno std :: reference_wrapper e non usando un modello variadico)
darune

4

Secondo lo standard §11.6.4 Inizializzazione elenco / p5 [dcl.init.list] [ Emphasis Mine ]:

Un oggetto di tipo 'std :: initializer_list' è costruito da un elenco di inizializzatori come se l'implementazione generasse e materializzasse (7.4) un valore di tipo "array di N const E" , dove N è il numero di elementi nell'elenco di inizializzatori. Ogni elemento di quell'array viene inizializzato per la copia con l'elemento corrispondente dell'elenco degli inizializzatori e l'oggetto std :: initializer_list viene costruito per fare riferimento a quell'array. [Nota: un costruttore o una funzione di conversione selezionata per la copia devono essere accessibili (clausola 14) nel contesto dell'elenco di inizializzatori. - end note] Se è richiesta una conversione di restringimento per inizializzare uno degli elementi, il programma è mal formato.

Pertanto, il compilatore si sta lamentando legittimamente (ovvero, auto &sdeduce int const& se non è possibile assegnarlo snel ciclo a distanza).

È possibile alleviare questo problema introducendo un contenitore anziché un elenco di inizializzatori (ad esempio, `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;
}

Dimostrazione dal vivo


@ Jarod42 Ouups scusate, modificato.
101010

La tua soluzione non soddisfa i miei criteri per una buona soluzione - se fossi stato contento non l'avrei chiesto in primo luogo :)
darune

anche la tua citazione non tenta di rispondere alla mia domanda
1919

1
@darune - in realtà, la citazione è il motivo per cui il tuo for (auto& s : {a, b, c, d})non funziona. Per quanto riguarda il motivo per cui lo standard ha quella clausola ..... dovresti chiedere ai membri del comitato di standardizzazione. Come molte altre cose, il ragionamento potrebbe essere qualsiasi tra "Non abbiamo considerato il tuo caso particolare abbastanza utile da preoccuparci di" fino a "Troppe altre parti dello standard dovrebbero cambiare per supportare il tuo caso e abbiamo rinviato la considerazione di tutto ciò fino a quando non svilupperemo uno standard futuro ".
Peter,

Non puoi semplicemente usare std::array<std::reference_wrapper>>?
Toby Speight,

1

Per soddisfare quella sintassi

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

potresti creare un 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;     
};

dimostrazione


1
In che cosa differisce std::reference_wrapper?
Toby Speight,

1
@TobySpeight: std::reference_wrapperrichiederebbe s.get() = 1;.
Jarod42,

0

Soluzione: utilizzare un wrapper di riferimento

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

Quindi utilizzato come:

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

Questo non tenta di rispondere alla prima domanda però.


-1

È possibile creare una classe wrapper per l'archiviazione di riferimenti e che avrà un operatore di assegnazione per aggiornare questo valore:

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

Dimostrazione dal vivo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.