Was ist der Vorteil der Verwendung von Weiterleitungsreferenzen in bereichsbasierten for-Schleifen?

115

const auto&würde ausreichen, wenn ich schreibgeschützte Operationen ausführen möchte. Ich bin jedoch darauf gestoßen

for (auto&& e : v)  // v is non-const

ein paar Mal in letzter Zeit. Das wundert mich:

Ist es möglich, dass in einigen dunklen Eckfällen die Verwendung von Weiterleitungsreferenzen im Vergleich zu auto&oder einen gewissen Leistungsvorteil bietet const auto&?

( shared_ptrist ein Verdächtiger für obskure Eckfälle)


Update Zwei Beispiele, die ich in meinen Favoriten gefunden habe:

Gibt es einen Nachteil bei der Verwendung von const-Referenzen beim Durchlaufen von Basistypen?
Kann ich die Werte einer Karte mithilfe einer bereichsbasierten for-Schleife problemlos durchlaufen?

Bitte konzentrieren Sie sich auf die Frage: Warum sollte ich auto && in bereichsbasierten for-Schleifen verwenden?

Ali
quelle
4
Sehen Sie es wirklich "oft"?
Leichtigkeitsrennen im Orbit
2
Ich bin mir nicht sicher, ob Ihre Frage genug Kontext enthält, um zu beurteilen, wie "verrückt" es ist, wo Sie es sehen.
Leichtigkeitsrennen im Orbit
2
@LightnessRacesinOrbit Lange Rede, kurzer Sinn: Warum sollte ich in bereichsbasierten auto&&for-Schleifen verwenden?
Ali

Antworten:

94

Der einzige Vorteil, den ich sehen kann, ist, wenn der Sequenziterator eine Proxy-Referenz zurückgibt und Sie diese Referenz auf eine nicht konstante Weise bearbeiten müssen. Betrachten Sie zum Beispiel:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}

Dies lässt sich nicht kompilieren , weil rvalue vector<bool>::referenceaus der zurück iteratorwird lvalue Referenz nicht binden an eine nicht-const. Aber das wird funktionieren:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}

Abgesehen davon würde ich nicht so codieren, wenn Sie nicht wüssten, dass Sie einen solchen Anwendungsfall erfüllen müssen. Dh ich würde dies grundlos nicht tun , weil es tut Ursache Menschen zu fragen , was Sie vorhaben. Und wenn ich es tun würde, würde es nicht schaden, einen Kommentar dazu aufzunehmen, warum:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}

Bearbeiten

Dieser letzte Fall von mir sollte wirklich eine Vorlage sein, um Sinn zu machen. Wenn Sie wissen, dass die Schleife immer eine Proxy-Referenz verarbeitet, autofunktioniert dies genauso gut wie auto&&. Aber wenn die Schleife manchmal Nicht-Proxy-Referenzen und manchmal Proxy-Referenzen handhabte, dann würde ich denke auto&&, die Lösung der Wahl werden.

Howard Hinnant
quelle
3
Auf der anderen Seite gibt es keinen expliziten Nachteil , oder? (Abgesehen von potenziell verwirrenden Menschen, die ich persönlich nicht sehr erwähnenswert finde.)
ildjarn
7
Ein anderer Begriff für das Schreiben von Code, der Menschen unnötig verwirrt, lautet: Schreiben von verwirrtem Code. Es ist am besten, Ihren Code so einfach wie möglich zu gestalten, aber nicht einfacher. Dies hilft dabei, den Fehlerzähler niedrig zu halten. Davon abgesehen, wenn && vertrauter wird, werden die Leute in vielleicht 5 Jahren ein Auto && Idiom erwarten (vorausgesetzt, es schadet tatsächlich nicht). Ich weiß nicht, ob das passieren wird oder nicht. Aber einfach liegt im Auge des Betrachters, und wenn Sie für mehr als nur sich selbst schreiben, berücksichtigen Sie Ihre Leser.
Howard Hinnant
7
Ich bevorzuge es, const auto&wenn der Compiler mir helfen soll, zu überprüfen, ob ich die Elemente in der Sequenz nicht versehentlich ändere.
Howard Hinnant
31
Ich persönlich verwende gerne auto&&generischen Code, bei dem ich die Elemente der Sequenz ändern muss. Wenn ich es nicht tue, bleibe ich einfach bei auto const&.
Xeo
7
@Xeo: +1 Aufgrund von Enthusiasten wie Ihnen, die ständig experimentieren und nach besseren Methoden suchen, entwickelt sich C ++ weiter. Danke dir. :-)
Howard Hinnant
26

Die Verwendung von auto&&oder universellen Referenzen mit einer forbereichsbasierten Schleife hat den Vorteil, dass Sie erfassen, was Sie erhalten. Für die meisten Arten von Iteratoren erhalten Sie wahrscheinlich entweder einen T&oder einen T const&für einen Typ T. Der interessante Fall ist, wenn die Dereferenzierung eines Iterators eine temporäre ergibt: C ++ 2011 hat entspannte Anforderungen und Iteratoren müssen nicht unbedingt einen l-Wert liefern. Die Verwendung universeller Referenzen entspricht der Argumentweiterleitung in std::for_each():

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}

Das Funktionsobjekt fkann behandeln T&, T const&und Tanders. Warum sollte der Körper einer forbereichsbasierten Schleife anders sein? Um den Typ tatsächlich unter Verwendung universeller Referenzen ableiten zu können, müssen Sie sie natürlich entsprechend weitergeben:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}

Verwenden std::forward()bedeutet natürlich, dass Sie alle zurückgegebenen Werte akzeptieren, aus denen verschoben werden soll. Ob solche Objekte in Nicht-Vorlagencode viel Sinn machen, weiß ich (noch?) Nicht. Ich kann mir vorstellen, dass die Verwendung universeller Referenzen dem Compiler mehr Informationen bieten kann, um das Richtige zu tun. Im Vorlagencode wird keine Entscheidung darüber getroffen, was mit den Objekten geschehen soll.

Dietmar Kühl
quelle
9

Ich benutze praktisch immer auto&&. Warum von einem Randfall gebissen werden, wenn Sie nicht müssen? Es ist auch kürzer zu tippen und ich finde es einfach ... transparenter. Wenn Sie verwenden auto&& x, wissen Sie jedes Mal xgenau *it, dass .

Hündchen
quelle
29
Mein Problem ist , dass Ihr sind Aufgeben const-ness mit , auto&&wenn const auto&genügt. Die Frage fragt nach den Eckfällen, in denen ich gebissen werden kann. Was sind die Eckfälle, die Dietmar oder Howard noch nicht erwähnt haben?
Ali
2
Hier ist ein Weg , gebissen werden , wenn Sie tun Einsatz auto&&. Wenn der Typ, den Sie erfassen, beispielsweise im Hauptteil der Schleife verschoben werden soll und zu einem späteren Zeitpunkt geändert wird, damit er in einen const&Typ aufgelöst wird, funktioniert Ihr Code stillschweigend weiter, Ihre Verschiebungen werden jedoch kopiert. Dieser Code wird sehr irreführend sein. Wenn Sie den Typ jedoch explizit als R-Wert-Referenz angeben, wird bei jedem, der den Containertyp geändert hat, ein Kompilierungsfehler
angezeigt,
@cyberbisson Ich bin gerade auf Folgendes gestoßen: Wie kann ich eine r-Wert-Referenz erzwingen, ohne den Typ explizit anzugeben? So etwas wie for (std::decay_t<decltype(*v.begin())>&& e: v)? Ich denke, es gibt einen besseren Weg ...
Jerry Ma
@JerryMa Es hängt davon ab , was Sie tun über die Art wissen. Wie oben erwähnt, auto&&wird eine "universelle Referenz" angegeben, sodass Sie durch alles andere einen spezifischeren Typ erhalten. Wenn vangenommen wird, dass es sich um a handelt vector, können Sie dies tun decltype(v)::value_type&&. Ich gehe davon aus, dass Sie dies wollten, indem Sie das Ergebnis operator*eines Iteratortyps verwenden. Sie können auch decltype(begin(v))::value_type&&die Typen des Iterators anstelle des Containers überprüfen. Wenn wir jedoch so wenig Einblick in den Typ haben, könnten wir es als etwas klarer betrachten, einfach mit auto&&...
Cyberbisson