operator overloading resolution


  • Mod

    Der Grund ist folgender. Fuer den ersten Schritt der Overload-Resolution wird die Konvertierung des Rueckgabetyps der Konvertierungsfunktion zum Zieltyp bool , also void** -> bool und bool -> 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, ICS j (F1) is a better conversion sequence than ICS j (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.

    Die Objektparameter sind einmal C& und C const& . Das Objektargument ist c , ein lvalue vom Typ C .
    Fuer die Konvertierung gilt

    Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
    [...]
    S1 and S2 are 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 by S1 refers.

    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 du operator bool non- const machst, kommt die zweite standard conversion sequence ueberhaupt ins Spiel: Und offensichtlich ist bool -> bool besser als void** -> bool .


  • Mod

    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. in

    bool 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?


  • Mod

    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. 👍


  • Mod

    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 C ist genau so gut wie C -> 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


  • Mod

    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 C ist genau so gut wie C -> 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.


  • Mod

    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!


Anmelden zum Antworten