Ist das hier standardkonform?
-
struct FooBar; struct Foo { Foo (const Foo& src); Foo (const FooBar* src); }; struct Bar { operator Foo (void) const; operator FooBar* (void) const; }; int main (void) { Bar b; Foo f = Foo (b); // <-- um diese Zeile geht es }Die Frage ist: was genau bewirkt f = Foo (b); hier?
1. <=> f = b.operator Foo ();
2. <=> f = Foo (b.operator FooBar* ());
3. <=> f = Foo (b.operator Foo ());
4. Mehrdeutigkeit zwischen zwei der oben genannten MöglichkeitenVisual C++ 2005 und Comeau kompilieren den Code anstandslos. Allerdings bin ich mir nicht so sicher, ob das richtig ist. Meiner Interpretation nach müßte eine Mehrdeutigkeit zwischen Möglichkeit 2 und 3 auftreten, da beides gleich viele implizite Typkonversionen beinhaltet, oder?
-
Variante 1 (als Initialisierung - das heißt, wir kopieren [evtl. ausgelassen] schon das Ergebnis des operators mit dem copy-ctor, nur kann man das nicht so wie in Variante 3 schreiben) ist äquivalent:
Foo (b);ist ein Funktionsstyle-Cast (und kein Konstruktoraufruf): deutlicher wird es, wenn wir C-Style schreiben:
(Foo)b;Es geht also nicht darum, einen der überladenen Konstruktoren von Foo auszuwählen, sondern ein Foo zu konstruieren (und diese Spitzfindigkeit ist eigentlich der ganze Witz an der Sache). Das geht nur über operator Foo, denn die Kette operator FooBar*,Foo(const FooBar*) wären ja zwei nutzerdefinierte Konversionen in einer Konvertierungssequenz.
Anmerkung: Die ganze Wahrheit ist das natürlich nicht. denn diese zweite Kette ist für heutige Compiler akzeptabel, auch wenn es dem Wortlaut des Standards widerspricht. Dieser Defekt ist essentiell, damit auto_ptr funktionieren kann (der Standard geht davon aus, dass auto_ptr irgendwie mit Magie arbeitet). In diesem Falle ist aber der direkte Aufruf des Konvertierungsoperators immer noch besser - ich würde ich da analog mit der letzten Alternative 13.3.3/1 argumentieren, die sich mit der Auswahl der besten Variante zwischen zwei user-defined Konvertierungssequenzen beschäftigt (sorry für den Sprachmix, aber elegantes Übersetzen von solchen technischen Begriffen ist nicht mein Ding). Du kannst es ja ausprobieren: dein Compiler dürfte es akzeptieren, wenn du einen der beiden operatoren weglässt.
-
Vielen Dank für deine Erläterungen. Ein paar Fragen hätte ich aber noch:
camper schrieb:
Das geht nur über operator Foo, denn die Kette operator FooBar*,Foo(const FooBar*) wären ja zwei nutzerdefinierte Konversionen in einer Konvertierungssequenz.
Dann hat also der Konstruktor Foo(const FooBar*) nicht die gleiche Priorität wie der Kopierkonstruktor?
camper schrieb:
Dieser Defekt ist essentiell, damit auto_ptr funktionieren kann (der Standard geht davon aus, dass auto_ptr irgendwie mit Magie arbeitet).
Könntest du das an einem Beispiel konkretisieren?
camper schrieb:
Du kannst es ja ausprobieren: dein Compiler dürfte es akzeptieren, wenn du einen der beiden operatoren weglässt.
Also nochmal kurz zusammengefaßt (trotz deiner ausführlichen und sehr erleuchtenden Erläuterungen bin ich mir immer noch nicht sicher): wenn der Compiler für obigen Code eine Mehrdeutigkeitsfehlermeldung bringt, bewegt er sich ebenso im Bereich des Standards, wie wenn er es nicht täte?
-
Jetzt bin auch ein wenig verwirrt. Hatte schon einen langen Artikel geschrieben, und bin dann gestolpert. Obige Erklärung dürfte falsch sein. Im Übrigen schluckt VC++2005 das nur, wenn man die Spracherweiterungen nicht abschaltet. Kein gutes Zeichen. Ich muss mir das im Standard noch einmal genauer anscheuen.
-
Vermutlich haben hier beide Compiler recht; die Frage, ob das Ganze eindeutig ist, hängt davon ab, wie rvalues an Referenzen gebunden werden - mithin Abschnitt 8.5.3. Dieser Teil des Standards ist nicht so ganz klar, im Übrigen wird es hier auch Änderungen in C++0x geben.
Ein Cast wie hier ist direkte Initialisierung, mithin wird aus allen möglichen Konstruktoren (anders als ich oben gesagt habe) der beste Ausgewählt.
Wir haben hier einmalFoo (const FooBar* src);um diesen benutzen zu können, brauchen wir den Konvertierungsoperator
operator FooBar* (void) const;hierbei stimmt der Typ noch nicht ganz, wir müssen eine Qualifikationskonvertierung anschließen von FooBar* nach const FooBar*
Für den Kandidaten
Foo(const Foo&)benötigen wir ein Foo als Argument, das kann entweder als Folge eines Konstruktoraufrufes (aber da haben wir nur Foo(const FooBar*)+operator FooBar*() und das ist unzulässig) oder eines Konvertierungsoperators wie hier:
operator Foo() constDie Schwierigkeit besteht darin, zu bestimmen, wie dann dieses rvalue an die Referenz des Copy-ctors gebunden wird. Müssen wir (sinngemäß - eigentlich geht es um ein zusätzliches temporary) das Foo erst in const Foo umqualifizieren (so wie sich VC++2005 verhält), dann wäre dieser Pfad nicht besser als der oben besprochene über Foo(const FooBar*) und das Ganze ist mehrdeutig. Folgen wir der Interpretation von Comeau (g++ dürfte hier auch einzuordnen sein), dann erfolgt hier keine Qualifizierung und die Sequenz ist folglich besser als die über FooBar*.