Unterschiedlicher Cast-Operator, der von verschiedenen Compilern aufgerufen wird

80

Betrachten Sie das folgende kurze C ++ - Programm:

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Wenn ich es auf verschiedenen Compilern kompiliere, erhalte ich verschiedene Ergebnisse. Mit Clang 3.4 und GCC 4.4.7 wird gedruckt true, während Visual Studio 2013 druckt false, was bedeutet, dass verschiedene Besetzungsoperatoren unter aufgerufen werden (bool)b. Welches ist das richtige Verhalten gemäß dem Standard?

In meinem Verständnis operator bool()keine Konvertierung benötigt, während operator int()wäre eine erfordern , intum boolUmwandlung, so dass der Compiler die ersten wählen soll. Hat constetwas damit zu tun, wird die Konstantenkonvertierung vom Compiler als "teurer" angesehen?

Wenn ich das entferne const, produzieren alle Compiler gleichermaßen falseals Ausgabe. Wenn ich dagegen die beiden Klassen miteinander kombiniere (beide Operatoren befinden sich in derselben Klasse), erzeugen alle drei Compiler eine trueAusgabe.

buc
quelle
3
Soweit ich weiß, hat B2 zwei Operatoren: B :: operator bool () und B2 :: operator int (). Beide Operatoren sind keine const-Operatoren und es gibt einen Unterschied zwischen const- und nicht-const-Versionen. Sie können die const-Version des Bools zu B und die const-Version des int zu B2 hinzufügen und die Änderungen anzeigen.
Tanuki
1
constist der Schlüssel hier. Da beide Operatoren gleich konstant sind oder nicht, verhalten sie sich wie erwartet, die boolVersion "gewinnt". Bei unterschiedlicher Konstanz "gewinnt" immer die Nicht-Konstanten-Version auf jeder Plattform. Mit abgeleiteten Klassen und unterschiedlicher Konstanz der Operatoren scheint VS einen Fehler zu haben, da es sich anders verhält als die beiden anderen Compiler.
buc
Könnten verschiedene Standardkonstruktoren für das unterschiedliche Verhalten verantwortlich sein?
ldgorman
16
EDG druckt auch true. Wenn GCC, Clang und EDG übereinstimmen und MSVC nicht einverstanden sind, bedeutet dies normalerweise, dass MSVC falsch ist.
Jonathan Wakely
1
Hinweis: In C ++ ist der Rückgabetyp im Allgemeinen nicht an der Überlastungsauflösung beteiligt.
Matthieu M.

Antworten:

51

Der Standard besagt:

Eine Konvertierungsfunktion in einer abgeleiteten Klasse verbirgt eine Konvertierungsfunktion in einer Basisklasse nur, wenn die beiden Funktionen in denselben Typ konvertiert werden.

§12.3 [class.conv]

Was bedeutet, dass operator booldas nicht verborgen ist operator int.

Der Standard besagt:

Während der Überlastungsauflösung ist das implizite Objektargument nicht von anderen Argumenten zu unterscheiden.

§13.3.3.1 [over.match.funcs]

Das "implizite Objektargument" ist in diesem Fall bvom Typ B2 &. operator boolerfordert const B2 &, so dass der Compiler const hinzufügen muss, bum aufzurufen operator bool. Dies - alles andere ist gleich - passt operator intbesser zusammen.

Der Standard besagt, dass a static_cast(das der Cast im C-Stil in diesem Fall ausführt) in einen Typ konvertiert werden kann T(in diesem Fall int), wenn:

Die Deklaration T t(e);ist für einige erfundene temporäre Variablen wohlgeformt t.

§5.2.9 [expr.static.cast]

Daher intkann das in a umgewandelt werden bool, und a boolkann gleichermaßen in a umgewandelt werden bool.

Der Standard besagt:

Die Konvertierungsfunktionen Sund ihre Basisklassen werden berücksichtigt. Diese nicht expliziten Konvertierungsfunktionen, die nicht in einem SErtragstyp verborgen sind, T oder ein Typ, der Tüber eine Standardkonvertierungssequenz in einen Typ konvertiert werden kann, sind Kandidatenfunktionen.

§13.3.1.5 [over.match.conv]

Der Überlastsatz besteht also aus operator intund operator bool. operator intWenn alle anderen Dinge gleich sind, ist dies eine bessere Übereinstimmung (da Sie keine Konstanz hinzufügen müssen). Daher operator intsollte ausgewählt werden.

Beachten Sie, dass der Standard (möglicherweise gegen die Intuition) den Rückgabetyp (dh den Typ, in den diese Operatoren konvertieren) nicht berücksichtigt, sobald sie dem Überlastungssatz hinzugefügt wurden (wie oben festgelegt), vorausgesetzt, die Konvertierungssequenz für die Argumente eines von Sie sind der Konvertierungssequenz für die Argumente des anderen überlegen (was in diesem Fall aufgrund der Konstanz der Fall ist).

Der Standard besagt:

Angesichts dieser Definitionen wird eine realisierbare Funktion F1 als eine bessere Funktion als eine andere lebensfähige Funktion F2 definiert, wenn für alle Argumente i ICSi (F1) keine schlechtere Konvertierungssequenz als ICSi (F2) ist, und dann

  • Für einige Argumente j ist ICSj (F1) eine bessere Konvertierungssequenz als ICSj (F2), oder, wenn nicht,
  • Der Kontext ist eine Initialisierung durch benutzerdefinierte Konvertierung, und die Standardkonvertierungssequenz vom Rückgabetyp F1 zum Zieltyp (dh der Typ der zu initialisierenden Entität) ist eine bessere Konvertierungssequenz als die Standardkonvertierungssequenz vom Rückgabetyp von F2 zum Zieltyp.

§13.3.3 [over.match.best]

In diesem Fall gibt es nur ein Argument (den impliziten thisParameter). Die Umwandlungsfolge für B2 &=> B2 &(to Call operator int) überlegen ist B2 &=> const B2 &(to Call operator bool) und daher operator intvon dem Überlastsatz , ohne Rücksicht auf die Tatsache ausgewählt wird , dass sie eigentlich nicht umwandeln nicht direkt an bool.

Robert Allan Hennigan Leahy
quelle
2
Sie können N3337, das (glaube ich) der erste Entwurf nach C ++ 11 war und hier nur sehr, sehr geringfügige Änderungen enthält , kostenlos herunterladen . Um den richtigen Teil des Standards zu finden, müssen Sie in der Regel die Abschnitte des PDF-Dokuments beurteilen, herausfinden, wo sich die Dinge befinden (indem Sie nachschlagen / den Standard zitieren) und mit STRG + F nach relevanten Schlüsselwörtern suchen.
Robert Allan Hennigan Leahy
1
@ikh Wo finde ich die aktuellen C- oder C ++ - Standarddokumente? ist die beste Frage dafür und deckt meines Wissens alle Entwürfe ab, die sowohl für C als auch für C ++ verfügbar sind.
Shafik Yaghmour
2
Das habe ich mit "wird erst angewendet, nachdem festgestellt wurde, welche Konvertierungssequenz besser ist". Ich denke jedoch, dass dies ein wichtiger Teil der Antwort ist: Es gibt (theoretisch) einen Konflikt zwischen der Konvertierung des implizierten Objektarguments und der "Konvertierung des Rückgabetyps", der durch die verschiedenen Schritte der Überlastungsauflösung eindeutig gelöst wird. [over.match.best] /1.4 weist deutlich auf die Trennung dieser Schritte hin und darauf, warum die endgültige Standardkonvertierungssequenz in diesem Fall keine Rolle spielt.
Dyp
2
Ich habe die Antwort bearbeitet, um Ihren Vorschlag aufzunehmen, und erläutert, warum der Rückgabetyp dieser Konvertierungsoperatoren bei der Auswahl des "besten" nicht berücksichtigt wird.
Robert Allan Hennigan Leahy
2
@RobertAllanHenniganLeahy Da dies der Punkt der Frage war, sollte es nicht irgendwo in der Antwort erscheinen, nicht nur in einem Kommentar?
Barmar
9

Kurz

Die Konvertierungsfunktion operator int()wird durch Klirren ausgewählt, operator bool() constda sie bnicht const-qualifiziert ist, wohingegen der Konvertierungsoperator für bool ist.

Die kurze Begründung ist, dass der Kandidat bei der Konvertierung bin boolare für die Überlastungsauflösung (mit impliziten Objektparametern) funktioniert

operator bool (B2 const &);
operator int (B2 &);

wo die zweite eine bessere Übereinstimmung ist, da bnicht const qualifiziert ist.

Wenn beide Funktionen dieselbe Qualifikation haben (entweder beide constoder nicht), operator boolwird ausgewählt, da dies eine direkte Konvertierung ermöglicht.

Konvertierung per Cast-Notation, Schritt für Schritt analysiert

Wenn wir uns einig sind, dass der boolesche ostream-Inserter (std :: basic_ostream :: operator << (bool val) gemäß [ostream.inserters.arithmetic]) mit dem Wert aufgerufen wird, der sich aus einer Konvertierung von bin boolergibt, können wir in diese Konvertierung graben .

1. Der Besetzungsausdruck

Die Besetzung von b zu bool

(bool)b

bewertet zu

static_cast<bool>(b)

gemäß C ++ 11, 5.4 / 4 [expr.cast], da const_castnicht anwendbar ist (hier keine const hinzufügen oder entfernen).

Diese statische Konvertierung ist gemäß C ++ 11, 5.2.9 / 4 [expr.static.cast] zulässig , wenn bool t(b);für eine erfundene Variable t gut ausgebildet ist. Solche Anweisungen werden als Direktinitialisierung gemäß C ++ 11, 8.5 / 15 [dcl.init] bezeichnet .

2. Direkte Initialisierung bool t(b);

In Abschnitt 16 des am wenigsten erwähnten Standardabsatzes heißt es (Hervorhebung von mir):

Die Semantik der Initialisierer ist wie folgt. Der Zieltyp ist der Typ des zu initialisierenden Objekts oder der zu initialisierenden Referenz, und der Quelltyp ist der Typ des Initialisierungsausdrucks.

[...]

[...] Wenn der Quelltyp ein (möglicherweise lebenslaufqualifizierter) Klassentyp ist, werden Konvertierungsfunktionen berücksichtigt.

Die anwendbaren Konvertierungsfunktionen werden aufgelistet, und die beste wird durch Überlastungsauflösung ausgewählt.

2.1 Welche Konvertierungsfunktionen stehen zur Verfügung?

Die verfügbaren Konvertierungsfunktionen sind operator int ()und operator bool() constda wie C ++ 11, 12.3 / 5 [class.conv] sagt uns:

Eine Konvertierungsfunktion in einer abgeleiteten Klasse verbirgt eine Konvertierungsfunktion in einer Basisklasse nur, wenn die beiden Funktionen in denselben Typ konvertiert werden.

Während C ++ 11 in 13.3.1.5/1 [over.match.conv] Folgendes angibt:

Die Konvertierungsfunktionen von S und seinen Basisklassen werden berücksichtigt.

Dabei ist S die Klasse, aus der konvertiert wird.

2.2 Welche Konvertierungsfunktionen sind anwendbar?

C ++ 11, 13.3.1.5/1 [over.match.conv] (Hervorhebung von mir):

1 [...] Unter der Annahme, dass "cv1 T" der Typ des zu initialisierenden Objekts und "cv S" der Typ des Initialisierungsausdrucks ist, wobei S ein Klassentyp ist, werden die Kandidatenfunktionen wie folgt ausgewählt: Die Konvertierung Funktionen von S und seinen Basisklassen werden berücksichtigt. Diese nicht expliziten Konvertierungsfunktionen, die nicht in S verborgen sind und Typ T ergeben, oder ein Typ, der über eine Standardkonvertierungssequenz in Typ T konvertiert werden kann, sind Kandidatenfunktionen.

Daher operator bool () constist anwendbar, da es nicht in verborgen ist B2und a ergibt bool.

Der Teil mit dem Schwerpunkt im letzten Standardzitat ist für die Konvertierung relevant, operator int ()da intein Typ ist, der über eine Standardkonvertierungssequenz in bool konvertiert werden kann. Die Konvertierung von intnach boolist nicht einmal eine Sequenz, sondern eine einfache direkte Konvertierung, die gemäß C ++ 11, 4.12 / 1 [conv.bool] zulässig ist.

Ein Wert von arithmetischer, nicht skalierter Aufzählung, Zeiger oder Zeiger auf Elementtyp kann in einen Wert vom Typ bool konvertiert werden. Ein Nullwert, ein Nullzeigerwert oder ein Nullelementzeigerwert wird in false konvertiert. Jeder andere Wert wird in true konvertiert.

Dies bedeutet, dass dies auch operator int ()anwendbar ist.

2.3 Welche Konvertierungsfunktion ist ausgewählt?

Die Auswahl der geeigneten Konvertierungsfunktion erfolgt über die Überlastungsauflösung ( C ++ 11, 13.3.1.5/1 [over.match.conv] ):

Die Überlastungsauflösung wird verwendet, um die aufzurufende Konvertierungsfunktion auszuwählen.

Es gibt eine besondere "Eigenart", wenn es um die Überlastungsauflösung für Klassenelementfunktionen geht: den impliziten Objektparameter ".

Per C ++ 11, 13.3.1 [over.match.funcs] ,

[...] sowohl statische als auch nicht statische Elementfunktionen haben einen impliziten Objektparameter [...]

Dabei lautet der Typ dieses Parameters für nicht statische Elementfunktionen gemäß Abschnitt 4:

  • "Wertreferenz auf Lebenslauf X" für Funktionen, die ohne Ref-Qualifier oder mit dem & Ref-Qualifier deklariert wurden

  • "R-Wert-Referenz auf Lebenslauf X" für Funktionen, die mit dem && ref-Qualifier deklariert wurden

Dabei ist X die Klasse, zu der die Funktion gehört, und cv die Lebenslaufqualifikation für die Deklaration der Mitgliedsfunktion.

Dies bedeutet, dass (gemäß C ++ 11, 13.3.1.5/2 [over.match.conv] ) bei einer Initialisierung durch Konvertierungsfunktion

Die Argumentliste enthält ein Argument, nämlich den Initialisiererausdruck. [Hinweis: Dieses Argument wird mit dem impliziten Objektparameter der Konvertierungsfunktionen verglichen. - Endnote]

Die Kandidatenfunktionen für die Überlastungsauflösung sind:

operator bool (B2 const &);
operator int (B2 &);

Offensichtlich operator int ()ist eine bessere Übereinstimmung, wenn eine Konvertierung unter Verwendung eines nicht konstanten Objekts vom Typ angefordert wird, B2da operator bool ()eine Qualifizierungskonvertierung erforderlich ist.

Wenn beide Konvertierungsfunktionen dieselbe konstante Qualifikation haben, reicht die Überlastungsauflösung dieser Funktion nicht mehr aus. In diesem Fall wird das Conversion- (Sequenz-) Ranking durchgeführt.

3. Warum wird operator bool ()ausgewählt, wenn beide Konvertierungsfunktionen dieselbe konstante Qualifikation haben?

Die Konvertierung von B2nach boolist eine benutzerdefinierte Konvertierungssequenz ( C ++ 11, 13.3.3.1.2 / 1 [over.ics.user] ).

Eine benutzerdefinierte Konvertierungssequenz besteht aus einer anfänglichen Standardkonvertierungssequenz, gefolgt von einer benutzerdefinierten Konvertierung, gefolgt von einer zweiten Standardkonvertierungssequenz.

[...] Wenn die benutzerdefinierte Konvertierung durch eine Konvertierungsfunktion angegeben wird, konvertiert die anfängliche Standardkonvertierungssequenz den Quelltyp in den impliziten Objektparameter der Konvertierungsfunktion.

C ++ 11, 13.3.3.2/3 [over.ics.rank]

[...] definiert eine teilweise Anordnung impliziter Konvertierungssequenzen basierend auf den Beziehungen Bessere Konvertierungssequenz und bessere Konvertierung.

[...] Benutzerdefinierte Konvertierungssequenz U1 ist eine bessere Konvertierungssequenz als eine andere benutzerdefinierte Konvertierungssequenz U2, wenn sie dieselbe benutzerdefinierte Konvertierungsfunktion oder Konstruktor- oder Aggregatinitialisierung enthalten und die zweite Standardkonvertierungssequenz von U1 besser ist als die zweite Standardumwandlungssequenz von U2.

Die zweite Standardkonvertierung ist der Fall von operator bool()is boolto bool(Identitätskonvertierung), während die zweite Standardkonvertierung bei operator int ()is intto booleine boolesche Konvertierung ist.

Daher ist die Konvertierungssequenz unter Verwendung operator bool ()besser, wenn beide Konvertierungsfunktionen dieselbe konstante Qualifikation aufweisen.

Pixelchemist
quelle
In den meisten Fällen ist es sehr intuitiv, dass das, was Sie mit dem Ergebnis tun, keinen Einfluss darauf hat, wie es berechnet wird. Wie wir berechnen, a/bist gleich, ob der Code ist float f = a/b;oder int f = a/b;. Dies ist jedoch ein Fall, in dem es etwas überraschend sein kann.
David Schwartz
1

Der C ++ - Bool-Typ hat zwei Werte - true und false mit den entsprechenden Werten 1 und 0. Die inhärente Verwirrung kann vermieden werden, wenn Sie einen Bool-Operator in der B2-Klasse hinzufügen, der den Bool-Operator der Basisklasse (B) explizit aufruft. Dann kommt die Ausgabe als falsch. Hier ist mein modifiziertes Programm. Dann bedeutet Operator Bool Operator Bool und keinesfalls Operator Int.

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
    operator bool() {
        return B::operator bool();
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

In Ihrem Beispiel hat (bool) b versucht, den bool-Operator für B2 aufzurufen, B2 hat den bool-Operator geerbt, und der int-Operator wird nach der Dominanzregel der int-Operator und der geerbte bool-Operator in B2 aufgerufen. Durch das explizite Vorhandensein eines Bool-Operators in der B2-Klasse selbst wird das Problem jedoch gelöst.

Dr. Debasish Jana
quelle
8
Es ist eine gute Lösung, ein Programm zu schreiben, aber es antwortet nicht, warum diese seltsame Sache im Detail auftritt.
Ikh
4
true and false with corresponding values 1 and 0Das ist eine Vereinfachung. truekonvertiert in eine Ganzzahl 1, aber das bedeutet nicht, dass es den Wert "hat" 1. In der Tat truekann 42darunter sein.
Leichtigkeitsrennen im Orbit
Ein expliziter Bool-Operator in B2 vermeidet die Verwirrung und Compiler-Abhängigkeiten dessen, was in B2, Operator int oder Operator bool aufgerufen wird.
Dr. Debasish Jana
Es gibt keine Compilerabhängigkeit. Der Standard verlangt, dass 0 in false und 1 in true konvertiert wird.
Welpe
1
@LightnessRacesinOrbit Mehr auf den Punkt, vielleicht: a hat boolnie den Wert 0 oder 1; Die einzigen Werte, die es annehmen kann, sind falseund true(die in 0 und 1 boolkonvertiert werden, wenn das in einen integralen Typ konvertiert wird).
James Kanze
0

Einige der vorherigen Antworten enthalten bereits viele Informationen.

Mein Beitrag ist, dass "Cast-Operationen" ähnlich wie "überladene Operationen" kompiliert werden. Ich schlage vor, für jede Operation eine Funktion mit einer eindeutigen Kennung zu erstellen und diese später durch den erforderlichen Operator oder Cast zu ersetzen.

#include <iostream>

class B {
public:
    bool ToBool() const {
        return false;
    }
};

class B2 : public B {
public:
    int ToInt() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << b.ToBool() << std::endl;
}

Wenden Sie später den Operator oder die Besetzung an.

#include <iostream>

class B {
public:
    operator bool() {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Nur meine 2 Cent.

umlcat
quelle