Warum wird diese Überlastung eines Konvertierungsoperators gewählt?

27

Betrachten Sie den folgenden Code .

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

Hier wird der zweite Konvertierungsoperator durch die Überlastauflösung ausgewählt. Warum?

Soweit ich es verstehe, sind die beiden Betreiber abgeleitet operator int &&() constund operator int &() constjeweils. Beide sind in der Reihe der realisierbaren Funktionen. Das Lesen von [over.match.best] hat mir nicht geholfen herauszufinden, warum letzteres besser ist.

Warum ist die letztere Funktion besser als die erstere?

Avakar
quelle
Ich bin eine einfache Katze und ich würde sagen, es muss die zweite sein, sonst wäre die Einführung der anderen eine bahnbrechende Änderung in C ++ 11 gewesen. Es wird irgendwo im Standard sein. Gute Frage, haben Sie eine positive Bewertung. (Achtung Experten: Wenn dieser Kommentar Hogwash ist, dann sag es mir bitte!)
Bathsheba
3
FWIW, template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;bringt es dazu, den ersten anzurufen.
NathanOliver
5
@ LightnessRaceswithMonica-Konvertierungsoperatoren erlauben dies, da es sonst keine Möglichkeit gibt, mehrere Konvertierungsoperatoren zu haben.
NathanOliver
1
@Bathsheba Ich glaube nicht, dass das der Grund dafür ist. Das ist so, als würde man sagen, dass Verschiebungskonstruktoren niemals durch Überlastungsauflösung ausgewählt werden können, da dies eine bahnbrechende Änderung wäre. Wenn Sie einen Verschiebungskonstruktor schreiben, entscheiden Sie sich dafür, dass Ihr Code durch diese Definition "beschädigt" wird.
Brian
1
@LightnessRaceswithMonica So wie ich es gerade im Kopf halte, behandle ich den Rückgabetyp als Namen der Funktion. Unterschiedliche Typen
bedeuten

Antworten:

13

Der zurückgegebene Konvertierungsoperator T&wird bevorzugt, da er spezialisierter ist als der zurückgegebene Konvertierungsoperator T&&.

Siehe C ++ 17 [temp.deduct.partial] / (3.2):

Im Rahmen eines Aufrufs einer Konvertierungsfunktion werden die Rückgabetypen der Konvertierungsfunktionsvorlagen verwendet.

und / 9:

Wenn für einen bestimmten Typen, Abzug in beiden Richtungen erfolgreich ist (dh die Typen gleich nach den Transformationen oben) und beide Pund Awaren Referenztypen (vor dem Typ ersetzt oben genannten): - wenn der Typ aus dem Argumente Vorlage war eine lvalue-Referenz und der Typ aus der Parametervorlage war nicht, der Parametertyp wird nicht als mindestens so spezialisiert angesehen wie der Argumenttyp; ...

Brian
quelle
12

Die abgeleiteten Rückgabewertumwandlungsoperatoren sind etwas seltsam. Die Kernidee ist jedoch, dass es sich wie ein Funktionsparameter verhält, um auszuwählen, welcher verwendet wird.

Und bei der Entscheidung zwischen T&&und T&den T&Gewinnen in den Überlastungsauflösungsregeln. Dies soll ermöglichen:

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

arbeiten. T&&kann mit einem l-Wert übereinstimmen, aber wenn sowohl der l-Wert als auch die universelle Referenzüberladung verfügbar sind, wird der l-Wert bevorzugt.

Der richtige Satz von Konvertierungsoperatoren ist wahrscheinlich:

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

oder auch

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

um zu verhindern, dass eine fehlgeschlagene Verlängerung der Lebensdauer Sie beißt.

3 Die zur Bestimmung der Reihenfolge verwendeten Typen hängen vom Kontext ab, in dem die Teilbestellung erfolgt:

[SNIP]

(3.2) Im Rahmen eines Aufrufs einer Konvertierungsfunktion werden die Rückgabetypen der Konvertierungsfunktionsvorlagen verwendet.

Was dann bei der Auswahl von Überladungen von "spezialisierteren" Regeln abhängt:

(9.1) Wenn der Typ aus der Argumentvorlage eine Wertreferenz war und der Typ aus der Parametervorlage nicht, wird der Parametertyp nicht als mindestens so spezialisiert wie der Argumenttyp angesehen. Andernfalls,

Somit operator T&&ist es nicht mindestens so spezialisiert wie operator T&, während kein Regelstaat operator T&nicht mindestens so spezialisiert ist wie operator T&&, also operator T&ist es spezialisierter als operator T&&.

Speziellere Vorlagen gewinnen Überlastungsauflösung über weniger, alles andere ist gleich.

Yakk - Adam Nevraumont
quelle
Jemand fügte das Sprachanwalt- Tag hinzu, als ich antwortete; Ich könnte einfach löschen. Ich habe eine vage Erinnerung daran, wie ich den Text gelesen habe, der besagt, dass er als Parameter behandelt wird, um auszuwählen, welcher aufgerufen wird, und den Text, der darüber spricht, wie &&vs &behandelt wird, wenn Vorlagenüberladungen ausgewählt werden, aber das Herausarbeiten des genauen Textes würde eine Weile dauern .
Yakk - Adam Nevraumont
4

Wir versuchen ein intvon einem zu initialisieren any. Der Prozess dafür ist es:

  1. Finde heraus, wie wir das machen können. Bestimmen Sie also alle unsere Kandidaten. Diese stammen aus den nicht expliziten Konvertierungsfunktionen, in die intüber eine Standardkonvertierungssequenz ( [over.match.conv] ) konvertiert werden kann . Der Abschnitt enthält diesen Satz:

    Ein Aufruf einer Konvertierungsfunktion, die "Referenz auf X" zurückgibt X, ist ein Gl-Wert vom Typ , und eine solche Konvertierungsfunktion wird daher als Xfür diesen Prozess der Auswahl von Kandidatenfunktionen ertragend angesehen .

  2. Wählen Sie den besten Kandidaten.

Nach Schritt 1 haben wir zwei Kandidaten. operator int&() constund operator int&&() constbeide werden zum intZwecke der Auswahl von Kandidatenfunktionen als nachgiebig angesehen. Welches ist der beste Kandidat, der nachgibt int?

Wir haben einen Tiebreaker, der l-Wert-Referenzen gegenüber r-Wert-Referenzen bevorzugt ( [over.ics.rank] /3.2.3 ). Wir binden hier jedoch keine Referenz, und das Beispiel dort ist etwas invertiert - dies gilt für den Fall, dass der Parameter eine Referenz von lvalue vs rvalue ist.

Wenn dies nicht zutrifft, wenden wir uns an den Tiebreaker [over.match.best] /2.5, der die speziellere Funktionsvorlage bevorzugt.

Im Allgemeinen gilt als Faustregel, dass eine spezifischere Konvertierung die beste Übereinstimmung darstellt. Die lWert-Referenzkonvertierungsfunktion ist spezifischer als die Weiterleitungsreferenzkonvertierungsfunktion, daher wird sie bevorzugt. Es gibt nichts an dem, was intwir initialisieren, was einen r-Wert erfordert (hätten wir stattdessen einen initialisiert int&&, operator T&() constwäre das kein Kandidat gewesen).

Barry
quelle
3.2.3 sagt tatsächlich T&&gewinnt, nicht wahr ? Ah, aber wir haben keinen Wert, an den wir gebunden sind. Oder wir? Bei der Auswahl unserer Kandidaten müssen einige Wörter definiert haben, wie wir gekommen sind, int&und int&&war das eine Bindung?
Yakk - Adam Nevraumont
@ Yakk-AdamNevraumont Nein, man zieht lWertreferenzen rWertreferenzen vor. Ich denke, das ist wahrscheinlich sowieso der falsche Abschnitt, und der "spezialisiertere" Tiebreaker ist der richtige.
Barry