2013-02-14 13 views
5

Soweit ich C++ 11 Referenzen verstehe, sollte ich nicht in der Lage sein, eine Rvalue-Referenz an eine (nicht konstante) Lvalue-Referenz zu binden, da die erstere an eine temporäre gebunden werden könnte und die letztere nie an eine gebunden werden soll vorübergehend.Warum kann ich std :: einen Stream rvalue ref in einen lvalue ref verschieben?

Jedoch fand ich dieses seltsame Verhalten in Verbindung temporäre Stromobjekte

struct Dummy {}; 
template <typename Stream> 
Stream& operator<<(Stream& s, Dummy) { 
    return s << ".";   // <- (A) 
} 

template <typename Stream> 
void pass(Stream&& s) { 
    std::move(s) << Dummy(); // <- (X) rvalue->lvalue conversion? 
} 

#include <fstream> 
int main() { 
    pass(std::fstream("test",std::ios::out)); 
} 

Wenn ich schreibe s << Dummy() in Zeile (X), C++ klagt in Linie (A), sagen

(was ich so weit, wie ich konnte reduziert)

error: invalid initialization of reference of type ‘std::basic_fstream<char>&’ from expression of type ‘std::basic_ostream<char>’

Doch warum tut der Code (wie oben gezeigt) kompiliert und funktioniert wie erwartet? Die von std::move zurückgegebene rvalue-Referenz sollte genauso wenig an eine lvalue-Referenz gebunden sein wie der Ausdruck s, aber sowohl gcc 4.6.1 als auch gcc 4.7.2 reagieren identisch.

Und warum scheint dieses Phänomen nur mit Streams zu funktionieren? Beim direkten Übergeben eines Dummy&& an eine Funktion, erwartet ein T& schlägt sowohl mit als auch ohne std::move.

Antwort

10

basic_ostream hat eine Überlastung der operator<< die wie folgt aussieht:

template <typename Elem, typename Traits, typename T> 
basic_ostream<Elem, Traits>& 
    operator<<(basic_ostream<Elem, Traits>&& sink, const T& val) 
{ 
    return sink << val; 
} 

Diese "Rvalue Strom insertion" in dem Standard bezeichnet wird, bei §27.7.3.9 [ostream.rvalue].

Erlaubt die implizite Umwandlung (von Arten) von einem rvalue basic_ostream zu einem Lvalue. Es wurde speziell eingeführt, um temporary streams to be usable without resorting to tricks zu ermöglichen.


Was, warum die Kompilierung schlägt fehl, wenn Sie den Schritt auslassen:

Wenn Stream& operator<<(Stream& s, Dummy)ohne die Bewegung genannt wird, wird Streamstd::fstream sein, die erbt von std::ostream (das heißt basic_ostream<char>).

Es wird die basic_ostream<E, T>& operator<<(basic_ostream<E, T>&, const char*) Überlastung verwenden Zeichenfolge einzufügen, dann versuchen, das Ergebnis des Ausdrucks zurückzukehren, die eine ostream sein wird. Sie können nicht implizit von std::ostream& zu std::fstream&, so dass Sie einen Fehler erhalten.

Sie können dieses Problem beheben, indem s auf seine eigene Linie zurückkehrt (wo es nicht implizit upcasted wurde.)

Dies ist kein Problem mit bewegen, weil Sie, dass rvalue-to-go durch Lvalue Insertion Operator, den wir gerade erst entdeckt haben. Innerhalb dieser Funktion ist der Stream ein basic_ostream und so Stream ist auch, und die Rückgabetypen übereinstimmen.

+1

Ich sehe. Das macht Sinn. Aber warum ist 'std :: move 'überhaupt nötig? – bitmask

+0

@bitmask: Weil ohne '' '' nicht an eine rvalue-Referenz gebunden werden kann, da es sich um einen lvalue handelt. –

+0

@KerrekSB: Aber es hat eine vollkommen gute Überladung für einen rvalue referenzierten Stream, an den man sich binden kann (wie in dieser Antwort gezeigt). Warum passt diese Überladung nicht ohne Unterstützung von 'std :: move'? – bitmask