operator overloading resolution
-
Mr.Long schrieb:
Das const hinter den Funktionen bedeutet, dass diese Funktionen
nurals erstes auf Konstante Objekte aufgerufen werden.Das const hinter der Funktion bedeutet, dass innerhalb der Funktion das Objekt(this) nicht verändert werden kann.
Mir ist nicht ganz klar wieso das Auswirkung auf die overload resolution haben sollte.
-
class A { public: operator bool() const { std::cout << "A is const" << std::endl; return false; } operator bool() { std::cout << "A isn't const" << std::endl; return true; } }; int main() { A a; const A c_a; static_cast<bool>(a); // A isn't const static_cast<bool>(c_a); // A is const }
-
Der Grund ist folgender. Fuer den ersten Schritt der Overload-Resolution wird die Konvertierung des Rueckgabetyps der Konvertierungsfunktion zum Zieltyp
bool, alsovoid**->boolundbool->bool, gar nicht beruecksichtigt, sondern nur die Konvertierung des Objektarguments zum Objektparameter:[over.match.best]/1 schrieb:
Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
— for some argument j,ICSj(F1)is a better conversion sequence thanICSj(F2), or, if not that,
— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type ofF1to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type ofF2to the destination type.Die Objektparameter sind einmal
C&undC const&. Das Objektargument istc, ein lvalue vom TypC.
Fuer die Konvertierung giltStandard conversion sequence
S1is a better conversion sequence than standard conversion sequenceS2if
[...]
—S1andS2are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized byS1refers.Das heisst kurz gesagt:
operator void**ist bei der Konvertierung des Objektarguments besser, daher wird die Konvertierung des Rueckgabetyps (die zweite standard conversion sequence) gar nicht erst beruecksichtigt.
Erst wenn beide Objektparameter gleich sind, also wenn duoperator boolnon-constmachst, kommt die zweite standard conversion sequence ueberhaupt ins Spiel: Und offensichtlich istbool-> bool besser alsvoid**->bool.
-
n3337 schrieb:
13.3.3 Best viable function [over.match.best]
1 Define ICSi(F) as follows:
— if F is a static member function, ICS1 (F) is defined such that ICS1 (F) is neither better nor worse than
ICS1 (G) for any function G, and, symmetrically, ICS1 (G) is neither better nor worse than ICS1 (F)133;
otherwise,
— let ICSi(F) denote the implicit conversion sequence that converts the i-th argument in the list to the
type of the i-th parameter of viable function F. 13.3.3.1 defines the implicit conversion sequences and
13.3.3.2 defines what it means for one implicit conversion sequence to be a better conversion sequence
or worse conversion sequence than another.
Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the
standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the
entity being initialized) is a better conversion sequence than the standard conversion sequence from
the return type of F2 to the destination type. [...]Bei der Auswahl der passenden Konvertierungsfunktion wird also (gedanklich) Überladungsauflösung zweimal durchgeführt.
Nachdem erst einmal alle potentiellen Kandidaten erfasst wurden, also z.B. inbool b = c;alle Operatoren ausser dem mit explicit markierten, betrachten wir zunächst die notwendige Konvertierung auf dem Argument c (in den jeweiligen Objektparameter), um die Funktion aufrufen zu können. die const-Überladungen benötigen dafür jeweils eine Qualifikationskonvertierung C -> const C, die void**-Variante kommt mit der sog. identischen Konvertierung aus. Alle diese Standardkonvertierungen haben zwar den selben Rang (=Exact Match), allerdings ist die identische Konvertierung eine echte Untersequenz der Qualifikationskonvertierung und folglich besser. Damit sind die const-Überladungen bereits eliminiert, und der zweite Schritt ist gar nicht mehr durchzuführen.
Das ist also nicht viel anders als gewöhnliche Überladungsauflösung:
1. Schritt: aufzählen aller möglichen Konvertierungswege,
2. Schritt: Auswahl der am leichtesten aufrufbaren Funktion, (gewöhnliche Überladung)
3. Schritt: falls 2. Schritt mehrere Kandidaten hinterlässt, Auswahl der Funktion mit der besten Konvertierung des Ergebnisses in den Zieltyp.
-
Also hat die Constness eines Castoperators groesseres Gewicht als der tatsaechliche Returntyp? Und das, obwohl auf diesem Weg eine zusaetzliche implizite Konvertierung erforderlich ist? Und obwohl es ueberhaupt kein Problem waere, "operator bool() const" auf einem Nicht-const-Objekt aufzurufen?
Wow.
Zusaetzliches Denkfutter:
Wenn der Typ von void zu int geaendert wird, dann findet der Compiler auch "operator int*() const". Bei Verwendung von void* wird auch in diesem Fall "operator void**()" aufgerufen. Warum?
-
Also hat die Constness eines Castoperators groesseres Gewicht als der tatsaechliche Returntyp?
Grob gesagt.
Wenn der Typ von void zu int geaendert wird, dann findet der Compiler auch "operator int*() const". Bei Verwendung von void* wird auch in diesem Fall "operator void**()" aufgerufen. Warum?
Mir ist nicht ganz klar was du meinst. Alle meine Interpretationen konnte ich (zumindest mit Clang) nicht nachvollziehen. Code?
-
Vielen Dank, Camper und Arcoth für die ausführliche Beschreibung, dass habe ich gesucht!
Nochmal Vielen Dank.
-
die const-Überladungen benötigen dafür jeweils eine Qualifikationskonvertierung C -> const C, die void**-Variante kommt mit der sog. identischen Konvertierung aus.
Die Argumentation ist falsch. Qualifikationskonvertierungen gibt es nur fuer Zeiger.
C -> const Cist genau so gut wieC -> C. Mein zweites Zitat ist der richtige Grund.Edit: Ich habs doch gesagt! Du bist meschugge!
-
Arcoth schrieb:
Wenn der Typ von void zu int geaendert wird, dann findet der Compiler auch "operator int*() const". Bei Verwendung von void* wird auch in diesem Fall "operator void**()" aufgerufen. Warum?
Mir ist nicht ganz klar was du meinst. Alle meine Interpretationen konnte ich (zumindest mit Clang) nicht nachvollziehen. Code?
Im originalen Code einfach alle "void"s durch "int"s ersetzen. Weiss nicht was Clang dazu sagt, aber VS und g++ machen das selbe. http://ideone.com/XbWpKh
-
Arcoth schrieb:
die const-Überladungen benötigen dafür jeweils eine Qualifikationskonvertierung C -> const C, die void**-Variante kommt mit der sog. identischen Konvertierung aus.
Die Argumentation ist falsch. Qualifikationskonvertierungen gibt es nur fuer Zeiger.
C -> const Cist genau so gut wieC -> C. Mein zweites Zitat ist der richtige Grund.Versuch mal ein Beispiel zu konstruieren, in dem diese Argumentation zu einem anderen Ergebnis führt (also im Vergleich zu: Binden an eine Referenz mit mehr cv-Qualifikation wird wie eine Qualifikationskonvertierung behandelt). Ich bin mir der Standardtextes durchaus bewusst, sehe aber keinen Grund, doppelt soviel zu schreiben, bloss weil der Standardtext unnötig umständlich ist.
-
surprisee schrieb:
Arcoth schrieb:
Wenn der Typ von void zu int geaendert wird, dann findet der Compiler auch "operator int*() const". Bei Verwendung von void* wird auch in diesem Fall "operator void**()" aufgerufen. Warum?
Mir ist nicht ganz klar was du meinst. Alle meine Interpretationen konnte ich (zumindest mit Clang) nicht nachvollziehen. Code?
Im originalen Code einfach alle "void"s durch "int"s ersetzen. Weiss nicht was Clang dazu sagt, aber VS und g++ machen das selbe. http://ideone.com/XbWpKh
void** kann nat. in void* konvertiert werden, denn jeder (cv-unqualifizierte) Objektzeiger kann nach void* konvertiert werden.
Eine Konvertierung int** -> int* gibt es dagegen nicht.
-
camper schrieb:
void** kann nat. in void* konvertiert werden, denn jeder (cv-unqualifizierte) Objektzeiger kann nach void* konvertiert werden.
Eine Konvertierung int** -> int* gibt es dagegen nicht.Oh Mann, da kam ja einiges zusammen. Alles standardkonform, aber in der Praxis reichlich kontraintuitiv und zu subtilen Fehlern fuehrend. ZB liefert die Verwendung im bool-Kontext a la "if (c)" immer true zurueck, da operator void**() verwendet wird und die Membervariable natuerlich immer an einer gueltigen Speicheradresse liegt... (Der Code sieht im Original nat. etwas anders aus, ist eine Wrapperklasse um Windows API HANDLEs, die eben als void* bzw. void** #definiert sind.)
Auch von mir nochmal vielen Dank, camper & Arcoth!