2015-05-27 27 views
12

ich zwei Klassen haben, von denen einer, sagen wir, für eine Zeichenfolge, und der andere kann in einen String umgewandelt werden:Mehrdeutige Zuweisungsoperator

class A { 
public: 
    A() {} 
    A(const A&) {} 
    A(const char*) {} 

    A& operator=(const A&) { return *this; } 
    A& operator=(const char*) { return *this; } 

    char* c; 
}; 
class B { 
public: 
    operator const A&() const { 
    return a; 
    } 
    operator const char*() const { 
    return a.c; 
    } 

    A a; 
}; 

Nun, wenn ich

B x; 
A y = x; 

tun Es löst den Kopierkonstruktor aus, der die Daten kompiliert. Aber wenn ich

tun
A y; 
y = x; 

Es beschwert sich über mehrdeutige Zuordnung und kann nicht wählen zwischen =(A&) und =(char*). Warum der Unterschied?

+3

Wahnsinn. Das ist alles was ich über die tiefsten Gruben von C++ sagen kann .... –

Antwort

5

Es gibt einen Unterschied zwischen Initialisierung und Zuordnung.

In Initialisierung, das heißt:

A y = x; 

Der eigentliche Aufruf von der Art der x abhängt. Wenn es die gleiche Art von y ist, dann wird es sein wie:

A y(x); 

Wenn nicht, wie in Ihrem Beispiel wird es sein:

A y(static_cast<const A&>(x)); 

Und das kompiliert in Ordnung, weil es keine Mehrdeutigkeit mehr.

In der Zuordnung gibt es keinen solchen Sonderfall, also keine automatische Auflösung der Mehrdeutigkeit.

Es ist erwähnenswert, dass:

A y(x); 

in Ihrem Code auch nicht eindeutig ist.

+3

Es könnte sich lohnen, darauf hinzuweisen, dass es in dieser Beschreibung einige Vereinfachungen gibt. Zum Beispiel ist 'static_cast' eigentlich eine implizite Konvertierung, die keine expliziten Konvertierungsfunktionen erlaubt. – dyp

+0

@dyp Wahr. Für eine genaue aber dichte Erklärung gibt es die Columbo-Antwort. – rodrigo

4

Es wird §13.3.1.4/(1.2), nur gehörend zu (Kopier-) Initialisierung von Objekten der Klasse-Typ, die angibt, wie Kandidaten Konvertierungsfunktionen für den ersten Fall zu finden sind:

Unter dem Bedingungen, die in 8.5 als Teil einer Kopierinitialisierung eines Objekts des Klassentyps angegeben sind, kann eine benutzerdefinierte Konvertierung aufgerufen werden, um einen Initialisierungsausdruck in den Typ des Objekts zu konvertieren, das initialisiert wird. Überladungsauflösung wird verwendet, um die benutzerdefinierte Konvertierung auszuwählen, die aufgerufen werden soll.

  • die Converting Konstruktoren (12.3.1) von: [...] Unter der Annahme, dass „CV1 T“ ist der Typ des Objekts initialisiert wird, wobei eine Klasse T Typ werden die Kandidaten-Funktionen wie folgt ausgewählt T sind Kandidaten Funktionen.

  • Wenn der Typ des initializer Ausdruck ein Klassentyp ist „cvS“, die nicht explizit Konvertierungsfunktionen von S und seine Klassen Basis betrachtet. Bei der Initialisierung eines temporären an gebunden werden den ersten Parameter eines Konstruktors, wobei der Parameter vom Typ "Referenz auf möglicherweise cv-qualifizierte T" und der Konstruktor mit einem einzigen Argument im Kontext der direkten Initialisierung von einem ist Objekt des Typs "cv2T", explizite Konvertierung Funktionen sind auch berücksichtigt. Diejenigen, die nicht innerhalb von S versteckt sind und einen Typ ergeben, dessen cv-unqualifizierte Version vom gleichen Typ wie T ist oder eine davon abgeleitete Klasse ist, sind Kandidatenfunktionen. [...] Konvertierungsfunktionen, die "Referenz auf X" zurückgeben, geben lvalues ​​oder xvalues, abhängig vom Referenztyp vom Typ X zurück und werden daher als X für diesen Prozess der Auswahl von Kandidatenfunktionen betrachtet.

D.h. operator const char* ist, obwohl in Betracht gezogen, nicht in der Kandidatenmenge enthalten, da const char* in keiner Hinsicht eindeutig A ist.In Ihrem zweiten Snippet wird operator= jedoch als gewöhnliche Elementfunktion aufgerufen, weshalb diese Einschränkung nicht mehr gilt. Sobald beide Konvertierungsfunktionen in dem Kandidatensatz sind, führt die Überladungsauflösung eindeutig zu einer Mehrdeutigkeit.

Beachten Sie, dass für die direkte Initialisierung die obige Regel auch nicht gilt.

B x; 
A y(x); 

ist schlecht gebildet.

Eine allgemeinere Form dieses Ergebnisses ist, dass niemals zwei benutzerdefinierte Konvertierungen in einer Konvertierungssequenz während der Überladungsauflösung möglich sind. Betrachten §13.3.3.1/4:

Wenn jedoch das Ziel

  • der erste Parameter eines Konstruktors oder [...]

und der Konstruktor [...] ein Kandidaten von

  • 13.3.1.3, wenn das Argument der temporäre im zweiten Schritt einer Klasse copy-Initialisierung oder
  • 13.3.1.4, 13.3.1.5, 13.3.1.6 oder (in allen Fällen),

benutzerdefinierte Konvertierungssequenzen werden nicht berücksichtigt. [] Hinweis: Diese Regeln verhindern, dass mehr als eine benutzerdefinierte Konvertierung während der Überladungsauflösung angewendet wird, wodurch eine unendliche Rekursion vermieden wird. - Ende note]

+0

TL; DR: Der Standard ist so geschrieben, dass es in einer Konvertierungssequenz nie zwei benutzerdefinierte Konvertierungen gibt. – dyp

+0

@dyp Ah, also Sie sagen, dass die Konvertierungssequenz nicht den Aufruf an den endgültigen Konstruktor namens - enthält aber den Aufruf an den impliziten temporären Konstruktor enthält, mit dem das Objekt initialisiert wird? – Columbo

+0

'T t = x;' ist die Definition einer impliziten Konvertierung. Sei 'X' der Typ von' x'. Wir können nur eine Umwandlung (Funktion) von "X" direkt zu "T" ODER einen Konstruktor von "T" erlauben, der ein "X" akzeptiert. Andernfalls würde es eine intermediäre Umwandlung von "X" zu "U" zu "T" geben. Eine Initialisierung 'T t (x);' ist keine implizite Konvertierung, ich frage mich, ob sie überhaupt als Konvertierung betrachtet wird. – dyp