Plage innocente basée sur une boucle qui ne fonctionne pas


11

Ce qui suit ne se compile pas :

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

Essayez-le sur Godbolt

L'erreur du compilateur est: error: assignment of read-only reference 's'

Maintenant, dans mon cas réel, la liste est composée de variables membres sur une classe.

Maintenant, cela ne fonctionne pas parce que l'expression devient un initializer_list<int>qui copie en fait a, b, c et d - donc ne permet pas non plus la modification.

Ma question est double:

Y a-t-il une motivation derrière ne pas permettre d'écrire une boucle for basée sur une plage de cette façon? par exemple. il pourrait peut-être y avoir un cas particulier pour les expressions d'accolade nue.

Quelle est une manière syntaxique soignée de réparer ce type de boucle?

Quelque chose dans ce sens serait préférable:

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

Je ne considère pas l'indirection de pointeur comme une bonne solution (c'est-à-dire {&a, &b, &c, &d}) - toute solution devrait donner la référence de l'élément directement lorsque l'itérateur est dé-référencé .


1
Une solution simple (que je ne me suis vraiment utiliser) est de créer une liste de pointeurs au lieu: { &a, &b, &c, &d }.
Un programmeur mec

2
initializer_listest principalement une vue sur le consttableau.
Jarod42

Ce que je ferais probablement, c'est d'initialiser explicitement les variables, une par une. Ce ne sera pas beaucoup plus à écrire, c'est clair et explicite, et ça fait ce qui est prévu. :)
Un programmeur mec

3
si vous ne voulez pas { &a, &b, &c, &d }, vous ne voudrez pas non plus:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

La question «pourquoi est-ce que cela ne fonctionne pas» est une question très différente de «que puis-je faire pour faire quelque chose comme ça?
Nicol Bolas

Réponses:


4

Les gammes ne sont pas aussi magiques que les gens le souhaiteraient. En fin de compte, il doit y avoir un objet sur lequel le compilateur peut générer des appels à une fonction membre ou à une fonction libre begin()et end().

Le plus proche que vous pourrez probablement venir est:

#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
Vous pouvez laisser tomber std::vector<int*>.
Jarod42

@mhhollomon J'ai explicitement déclaré que je n'étais pas intéressé par une solution d'indirection de pointeur.
darune

1
Cela devrait être auto sou auto* snon auto& s.
LF

@darune - Je serai heureux que quelqu'un donne une réponse différente. Il n'est pas clair qu'une telle réponse existe avec la norme actuelle.
mhhollomon

@LF - d'accord.
mhhollomon

4

Juste une autre solution dans une idée d'emballage:

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

Alors:

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

les sorties

0000
1111

2
Il s'agit d'une solution / contournement décente et bonne. C'est une idée similaire à ma propre réponse (j'ai utilisé un std :: reference_wrapper et n'utilise pas de modèle
variadic

4

Selon la norme §11.6.4 Initialisation de liste / p5 [dcl.init.list] [ Emphasis Mine ]:

Un objet de type 'std :: initializer_list' est construit à partir d'une liste d'initialiseurs comme si l'implémentation générait et matérialisait (7.4) une valeur de type "tableau de N const E" , où N est le nombre d'éléments dans la liste d'initialiseurs. Chaque élément de ce tableau est initialisé en copie avec l'élément correspondant de la liste d'initialisation, et l'objet std :: initializer_list est construit pour faire référence à ce tableau. [Remarque: Un constructeur ou une fonction de conversion sélectionnée pour la copie doit être accessible (article 14) dans le contexte de la liste d'initialisation. - note de fin] Si une conversion plus étroite est nécessaire pour initialiser l'un des éléments, le programme est mal formé.

Ainsi, votre compilateur se plaint légitimement (c'est-à-dire, auto &sdéduit int const& set vous ne pouvez pas attribuer às dans la boucle à distance).

Vous pouvez atténuer ce problème en introduisant un conteneur au lieu d'une liste d'initialisation (par exemple, `std :: vector ') avec' 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;
}

Démo en direct


@ Jarod42 Ouups désolé, modifié cela.
101010

Votre solution ne correspond pas à mes critères pour une belle solution - si j'étais satisfait de cela, je ne l'aurais pas demandé en premier lieu :)
darune

aussi votre citation n'essaye pas de répondre à ma question
darune

1
@darune - en fait, la citation est la raison pour laquelle votre for (auto& s : {a, b, c, d})ne fonctionne pas. Quant à savoir pourquoi la norme contient cet article ... vous devriez demander aux membres du comité de normalisation. Comme beaucoup de ces choses, le raisonnement pourrait être n'importe quoi entre "Nous n'avons pas considéré que votre cas particulier était suffisamment utile pour se soucier de" jusqu'à "Trop d'autres parties de la norme devraient changer pour soutenir votre cas, et nous avons reporté l'examen de tout cela jusqu'à ce que nous développions une future norme ".
Peter

Ne pouvez-vous pas simplement utiliser std::array<std::reference_wrapper>>?
Toby Speight

1

Pour satisfaire cette syntaxe

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

vous pouvez créer 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;     
};

Démo


1
En quoi cela diffère- std::reference_wrappert-il?
Toby Speight

1
@TobySpeight: std::reference_wrappernécessiterait s.get() = 1;.
Jarod42

0

Solution: utilisez un wrapper de référence

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

Puis utilisé comme:

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

Cependant, cela n'essaie pas de répondre à la première question.


-1

Vous pouvez créer une classe wrapper pour stocker la référence et qui aura l'opérateur d'affectation pour mettre à jour cette valeur:

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

Démo en direct

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.