Unschuldiger Bereich basierend auf Schleife funktioniert nicht


11

Folgendes wird nicht kompiliert:

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

Probieren Sie es auf Godbolt

Compilerfehler ist: error: assignment of read-only reference 's'

In meinem aktuellen Fall besteht die Liste nun aus Mitgliedsvariablen einer Klasse.

Dies funktioniert jetzt nicht, da der Ausdruck zu einem wird initializer_list<int>, der tatsächlich a, b, c und d kopiert - und daher auch keine Änderung zulässt.

Meine Frage ist zweifach:

Gibt es eine Motivation, auf diese Weise keine bereichsbasierte for-Schleife schreiben zu dürfen? z.B. Vielleicht könnte es einen Sonderfall für nackte Zahnspangen geben.

Was ist eine syntaktisch saubere Methode, um diese Art von Schleife zu reparieren?

Etwas in dieser Richtung wäre vorzuziehen:

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

Ich halte Zeiger-Indirektion nicht für eine gute Lösung (das heißt {&a, &b, &c, &d}) - jede Lösung sollte die Elementreferenz direkt angeben, wenn der Iterator de-referenziert wird .


1
Eine einfache Problemumgehung (die ich selbst nicht wirklich verwenden würde) besteht darin, stattdessen eine Liste von Zeigern zu erstellen : { &a, &b, &c, &d }.
Einige Programmierer Typ

2
initializer_listist meistens eine Ansicht auf constArray.
Jarod42

Was ich wahrscheinlich tun würde, ist, die Variablen nacheinander explizit zu initialisieren. Es wird nicht viel mehr zu schreiben sein, es ist klar und deutlich und es tut, was beabsichtigt ist. :)
Einige Programmierer Typ

3
Wenn Sie nicht wollen { &a, &b, &c, &d }, wollen Sie auch nicht:for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

Die Frage "Warum kann das nicht funktionieren?" Ist eine ganz andere Frage als "Was kann ich tun, damit so etwas funktioniert?".
Nicol Bolas

Antworten:


4

Bereiche sind nicht so magisch, wie die Leute es gerne hätten. Am Ende muss es ein Objekt geben, mit dem der Compiler entweder eine Mitgliedsfunktion oder eine freie Funktion begin()und aufrufen kann end().

Am nächsten kommen Sie wahrscheinlich:

#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
Du kannst fallen lassen std::vector<int*>.
Jarod42

@mhhollomon Ich habe ausdrücklich erklärt, dass ich nicht an einer Zeiger-Indirektionslösung interessiert bin.
Darune

1
Es sollte sein auto soder auto* snicht auto& s.
LF

@darune - Ich bin froh, dass jemand eine andere Antwort gibt. Es ist nicht klar, dass eine solche Antwort mit dem aktuellen Standard existiert.
Mhhollomon

@LF - vereinbart.
Mhhollomon

4

Nur eine weitere Lösung innerhalb einer Wrapper-Idee:

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

Dann:

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

Ausgänge

0000
1111

2
Dies ist eine anständige und gute Lösung / Problemumgehung. Es ist eine Idee, die meiner eigenen Antwort ähnelt (ich habe einen std :: reference_wrapper verwendet und keine variable Vorlage verwendet)
darune

4

Gemäß dem Standard §11.6.4 Listeninitialisierung / p5 [dcl.init.list] [ Emphasis Mine ]:

Ein Objekt vom Typ 'std :: initializer_list' wird aus einer Initialisierungsliste erstellt, als ob die Implementierung einen Wert vom Typ "Array von N const E" generiert und materialisiert (7.4) hätte , wobei N die Anzahl der Elemente in der Initialisierungsliste ist. Jedes Element dieses Arrays wird mit dem entsprechenden Element der Initialisierungsliste kopierinitialisiert, und das Objekt std :: initializer_list wird so konstruiert, dass es auf dieses Array verweist. [Hinweis: Auf einen für die Kopie ausgewählten Konstruktor oder eine Konvertierungsfunktion muss im Kontext der Initialisierungsliste zugegriffen werden können (Abschnitt 14). - Endnote] Wenn eine Verengungskonvertierung erforderlich ist, um eines der Elemente zu initialisieren, ist das Programm fehlerhaft.

Daher beschwert sich Ihr Compiler zu Recht (dh auto &szieht ab int const& sund Sie können nicht sin der for-Schleife für den Bereich zuweisen ).

Sie können dieses Problem beheben, indem Sie einen Container anstelle einer Initialisiererliste (z. B. "std :: vector") mit "std :: reference_wrapper" einführen:

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

Live-Demo


@ Jarod42 Ouups sorry, habe das geändert.
101010

Ihre Lösung entspricht nicht meinen Kriterien für eine schöne Lösung - wenn ich damit zufrieden wäre, hätte ich nicht an erster Stelle gefragt :)
Darune

Auch Ihr Zitat versucht nicht, meine Frage zu beantworten
Darune

1
@darune - eigentlich ist das Zitat der Grund, warum Ihr for (auto& s : {a, b, c, d})nicht funktioniert. Warum der Standard diese Klausel enthält ..... Sie müssten die Mitglieder des Standardisierungsausschusses fragen. Wie bei vielen solchen Dingen könnte die Argumentation zwischen "Wir haben Ihren speziellen Fall nicht als nützlich genug angesehen, um uns darum zu kümmern" und "Zu viele andere Teile des Standards müssten geändert werden, um Ihren Fall zu unterstützen, und wir haben die Prüfung verschoben." von all dem, bis wir einen zukünftigen Standard entwickeln ".
Peter

Kannst du nicht einfach benutzen std::array<std::reference_wrapper>>?
Toby Speight

1

Um diese Syntax zu erfüllen

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

Sie könnten einen Wrapper erstellen:

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


1
Wie unterscheidet sich das von std::reference_wrapper?
Toby Speight

1
@TobySpeight: std::reference_wrapperwürde erfordern s.get() = 1;.
Jarod42

0

Lösung: Verwenden Sie einen Referenz-Wrapper

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

Dann verwendet als:

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

Dies versucht jedoch nicht, die erste Frage zu beantworten.


-1

Sie können eine Wrapper-Klasse zum Speichern von Referenzen erstellen, die über einen Zuweisungsoperator verfügt, um diesen Wert zu aktualisieren:

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

Live-Demo

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.