Implizite Konvertierungen (Split aus "Operatoren für eigene Double Klasse")
-
Nexus schrieb:
hustbaer schrieb:
IIRC sind mehrere implizite userdefinierte Konvertierungen "am Stück" nicht erlaubt, bzw. der Compiler sucht einfach nicht danach.
Sollte schon gehen. Hat man häufiger, als man denkt:
struct MyClass { MyClass(std::string s); }; MyClass a("hallo");Hier erfolgt nur eine userdefinierte implizite Konvertierung (char const [N] -> string), die zweite ist explizit (string -> myclass).
Probier mal das, das geht nämlich nimmer:
struct MyClass { MyClass(std::string s); }; void foo(MyClass mc) { } void bar() { foo("sepp"); // nix da! foo(static_cast<MyClass>("sepp")); // geht, da nur eine implizite userdefinierte konvertierung in folge foo(static_cast<std::string>("sepp")); // geht auch, da auch nur eine implizite userdefinierte konvertierung in folge }Für
foo("sepp")wären nämlich zwei implizite userdefinierte Konvertierungen nötig, und das ist wie gesagt nicht erlaubt. Wäre auch totale Katastrophe wenns erlaubt wäre, nicht nur was die Compilezeiten angeht, auch was das "principle of least astonishment" angeht.("userdefiniert" schliesst hier natürlich sämtliche Typen der Standard-Library mit ein - nur halt nicht "eingebaute" Konvertierungen wie char -> int oder int -> double -- sollte klar sein)
Es geht sogar hin und zurück zum ursprünglichen Typen. Das wird bei
std::auto_ptr(undstd::auto_ptr_ref) ausgenutzt, um Move-Semantik über Parameter und Rückgabewerte hinweg zu ermöglichen.Nönönönönö, da ist auch immer nur eine implizite Konvertierung am Stück.
-
hustbaer schrieb:
Probier mal das, das geht nämlich nimmer:
Stimmt. Mein Beispiel war aber auch Schrott. Was ist hier?
struct MyClass { MyClass(std::string s); }; int main() { MyClass a = "hallo"; }Ich sehe nämlich keine explizite Konvertierung.
Oder wie siehts hier aus?
std::auto_ptr<int> Function() { return std::auto_ptr<int>(); } int main() { std::auto_ptr<int> x = Function(); }Zwischendurch wird ein Objekt vom Typ
std::auto_ptr_ref<int>erzeugt. Wenn die Konvertierung nicht wäre, würde der Compiler versuchen, den Kopierkonstruktor vonstd::auto_ptr<int>aufzurufen. Der nimmt jedoch nur eine Non-Const-Referenz, kann also nicht auf temporäre Objekte angewandt werden.
-
Nexus schrieb:
Was ist hier?
struct MyClass { MyClass(std::string s); }; int main() { MyClass a = "hallo"; }Ich sehe nämlich keine explizite Konvertierung.
Hier wird versucht, ein Objekt des Typs MyClass zu erstellen. Dafür wird nach einem geeigneten Konstruktor gesucht. Dein Konstruktor nimmt einen std::string entgegen. Also ist std::string der Zieltyp und nicht MyClass. Deshalb gibts auch nur eine implizite Konvertierung, nämlich von const char* nach std::string. Die andere Konvertierung gibst du sozusagen direkt an, indem du durch das Definieren eines neuen MyClass-Objekts einen Konstruktoraufruf forderst und dadurch std::string -> MyClass "konvertiert" werden muss.
-
Nexus schrieb:
hustbaer schrieb:
Probier mal das, das geht nämlich nimmer:
Stimmt. Mein Beispiel war aber auch Schrott. Was ist hier?
struct MyClass { MyClass(std::string s); }; int main() { MyClass a = "hallo"; }Ich sehe nämlich keine explizite Konvertierung.
Ich dachte dass das gleichbedeutend ist mit
a("hallo"). MSVC sieht das auch so wie ich. Comeau sieht es anders, der compiliert das nicht. Muss ich selbst im Standard nachsehen. Wenn es erlaubt ist, dann deswegen, weil im Kontext einer Initialisierung die SchreibweiseMyClass a = "hallo"gleichbedeutend ist mitMyClass a("hallo"). Bzw. eben nicht, wenn es nicht erlaubt ist
(d.h. gleichbedeutend mit der Ausnahme, dass der Copy-Ctor "accessible" sein muss)Oder wie siehts hier aus?
std::auto_ptr<int> Function() { return std::auto_ptr<int>(); } int main() { std::auto_ptr<int> x = Function(); }Zwischendurch wird ein Objekt vom Typ
std::auto_ptr_ref<int>erzeugt. Wenn die Konvertierung nicht wäre, würde der Compiler versuchen, den Kopierkonstruktor vonstd::auto_ptr<int>aufzurufen. Der nimmt jedoch nur eine Non-Const-Referenz, kann also nicht auf temporäre Objekte angewandt werden.Das ist ein klarer Fall, hier wird nur eine implizite userdefinierte Konvertierung gemacht, und zwar von std::auto_ptr zu std::auto_ptr_ref. Das std::auto_ptr_ref Objekt wird dann direkt vom passenden auto_ptr::operator = (auto_ptr_ref) gefressen (EDIT: bzw. vom passenden ctor - kommt aufs selbe raus - kannst mal den ctor MyDysfunctionalAutoPtr(Ref r) wegmachen und gucken was passiert /EDIT). Ohne den geht's auch nicht:
struct MyDysfunctionalAutoPtr { struct Ref {}; MyDysfunctionalAutoPtr() {} MyDysfunctionalAutoPtr(MyDysfunctionalAutoPtr& mutableOther) {} MyDysfunctionalAutoPtr(Ref r) {} MyDysfunctionalAutoPtr& operator = (MyDysfunctionalAutoPtr& mutableOther) { return *this; } operator Ref () { return Ref(); } }; struct MyBetterAutoPtr { struct Ref {}; MyBetterAutoPtr() {} MyBetterAutoPtr(MyBetterAutoPtr& other) {} MyBetterAutoPtr(Ref r) {} operator Ref () { return Ref(); } MyBetterAutoPtr& operator = (MyBetterAutoPtr& mutableOther) { return *this; } MyBetterAutoPtr& operator = (Ref r) { return *this; } }; MyDysfunctionalAutoPtr MachMirEinAuto() { return MyDysfunctionalAutoPtr(); } MyBetterAutoPtr MachMirEinBesseresAuto() { return MyBetterAutoPtr(); } int main() { MyDysfunctionalAutoPtr aa(MachMirEinAuto()); // geht MyDysfunctionalAutoPtr ab = MachMirEinAuto(); // geht auch MyDysfunctionalAutoPtr ac; ac = MachMirEinAuto(); // geht nicht, da MyDysfunctionalAutoPtr keinen operator = (Ref) hat, // und ohne den 2 userdefinierte konvertierungen nötig wären MyBetterAutoPtr baa(MachMirEinBesseresAuto()); // geht MyBetterAutoPtr bab = MachMirEinBesseresAuto(); // geht auch MyBetterAutoPtr bac; bac = MachMirEinBesseresAuto(); // geht, da MyBetterAutoPtr einen operator = (Ref) hat }Comeau C/C++ 4.3.10.1 schrieb:
"ComeauTest.c", line 37: error: no operator "=" matches these operands operand types are: MyDysfunctionalAutoPtr = MyDysfunctionalAutoPtr ac = MachMirEinAuto(); // geht nicht, da MyDysfunctionalAutoPtr keinen operator = (Ref) hat, ^ 1 error detected in the compilation of "ComeauTest.c".
-
Michael E. schrieb:
Die andere Konvertierung gibst du sozusagen direkt an, indem du durch das Definieren eines neuen MyClass-Objekts einen Konstruktoraufruf forderst und dadurch std::string -> MyClass "konvertiert" werden muss.
Ja, aber die Konvertierung
std::string->MyClassgeschieht implizit (schliesslich ist der String kein direktes Konstruktorargument und wird auch nicht gecastet). Da der Konvertierungskonstruktor nichtexplicitist, spielt das aber keine Rolle.hustbaer schrieb:
Ich dachte dass das gleichbedeutend ist mit
a("hallo").Bei
MyClass a = "hallo";darf der Konstruktor nichtexplicitsein. Zudem ist es meines Wissens erlaubt, dass der Compiler vom String ein temporäres Objekt anlegt und dann dieses in das benannte Objekt kopiert.Es kann aber sein, dass hier MSVC++ nicht ganz so streng ist. Das wäre aber noch gut zu wissen, wenn man portabel programmieren möchte...
hustbaer schrieb:
Das ist ein klarer Fall, hier wird nur eine implizite userdefinierte Konvertierung gemacht, und zwar von std::auto_ptr zu std::auto_ptr_ref. Das std::auto_ptr_ref Objekt wird dann direkt vom passenden auto_ptr::operator = (auto_ptr_ref) gefressen
Du hast Recht. Danke vielmals für die Mühe.
Dabei habe ich selbst einmal einen solchen Smart-Pointer geschrieben, eigentlich sollte ich das wissen

@ Student83: Sorry für Offtopic. Jemand ist sicher so nett das abzutrennen

-
Nexus schrieb:
Was ist hier?
struct MyClass { MyClass(std::string s); }; int main() { MyClass a = "hallo"; }Ich sehe nämlich keine explizite Konvertierung.
Das funktioniert nicht. Du bräuchtest 2 benutzerdefinierte Konvertierungen hierfür. Eine von char[] nach string und eine von string nach MyClass bevor damit (logisch) der Kopierkonstruktor aufgerufen wird. (Letzteres darf aber wegoptimiert werden. Ggf wird auch einfach ein Move-Constructor benutzt.) Diese Art der Initialisierung (T a=...) nennt sich "Kopierinitialisierung" (copy-initialization) und ist übrigens genau dasselbe, wie die Art von Initialisierung von Funktionsparametern sowie des Rückgabewertes/objekts einer Funktion. Und das hier
int main() { MyClass a ("hallo"); }ist eine direkte Initialisierung (direct initialization). Hier würde genau eine benutzerdefinierte Konvertierung benutzt (char[]-->string), um den Parameter des Konstruktors zu initialisieren.
kk
-
Nexus schrieb:
hustbaer schrieb:
Ich dachte dass das gleichbedeutend ist mit
a("hallo").Bei
MyClass a = "hallo";darf der Konstruktor nichtexplicitsein. Zudem ist es meines Wissens erlaubt, dass der Compiler vom String ein temporäres Objekt anlegt und dann dieses in das benannte Objekt kopiert.Es kann aber sein, dass hier MSVC++ nicht ganz so streng ist. Das wäre aber noch gut zu wissen, wenn man portabel programmieren möchte...
In meinem Test-Code war auch kein Ctor "explicit"... hm.
Muss ich nochmal probieren.Ich hatte auch bei MSVC die non-Standard Extensions an (hatte auf die Schnelle kein Projekt parat wo die abgedreht wären bzw. das überhaupt ohne compilieren würde).
Und mit MS-Extensions tut der pöse MSVC ja sogar munter rvalues an mutable Referenzen binden...
(Daher hab ich meinen AutoPtr Test-Code auch durch den Comeau gejagt)
-
krümelkacker schrieb:
Das funktioniert nicht.
Okay, dann ist es also definitiv eine Erweiterung von MSVC++. Danke für die klare Antwort.
hustbaer schrieb:
Ich hatte auch bei MSVC die non-Standard Extensions an (hatte auf die Schnelle kein Projekt parat wo die abgedreht wären bzw. das überhaupt ohne compilieren würde).
Und mit MS-Extensions tut der pöse MSVC ja sogar munter rvalues an mutable Referenzen binden...
Bei den RValues warnt er wenigstens, wenn man es versucht. Aber ich habs nun mit abgeschalteten Erweiterungen versucht, dann wird die Konvertierung tatsächlich als Fehler gewertet. Lustig ist auch, wie oft ich dann ein unerwartetes Dateiende hatte

Blöd ist nur, dass man etliche Windows-Header so nicht benutzen kann (wegen $ in Makros etc.)
Da ist mir gleich was anderes aufgefallen: Wie müsste die Deklaration hier richtig aussehen, wenn ich nur dem
OtherClass-Template mit gleichemTFreundschaft gewähren will?template <typename T> class MyClass { friend class OtherClass<T>; };
-
Nexus schrieb:
Da ist mir gleich was anderes aufgefallen: Wie müsste die Deklaration hier richtig aussehen, wenn ich nur dem
OtherClass-Template mit gleichemTFreundschaft gewähren will?template <typename T> class MyClass { friend class OtherClass<T>; };Keine Ahnung, was der Standard dazu sagt, aber VS 2008 schlluckt das, wenn OtherClass bereits deklariert wurde:
#include <iostream> #include <string> template<typename T> struct Foo; // wichtig! template<typename T> class Bar { friend struct Foo<T>; static const int value = 42; }; template<typename T> struct Foo { template<typename U> void foo(const Bar<U>& arg) { std::cout << arg.value << std::endl; } }; int main() { { Bar<int> bar; Foo<int> foo; foo.foo(bar); // kompiliert } { Bar<int> bar; Foo<std::string> foo; foo.foo(bar); // kompiliert nicht: kein Zugriff auf Bar<int>::value } }Edit: Laut Standard müsste obiger Code legal sein. Ob die Vorwärtsdeklaration nötig ist, kann ein anderer herausfinden.
Beispiel aus dem Standard (§14.5.3 Absatz 1):
template<class T> class task; template<class T> task<T>* preempt(task<T>*); template<class T> class task { ... friend class task<int>;
-
Boah, bei Template-Friends muss ich selber immer nachgucken, das merk ich mir einfach nicht.
Und mit nachgucken meine ich so lange gockeln bis ich was finde wascompiliert- ob das dann laut Standard OK ist oder nicht ... pfuh
-
Die Vorwärtsdeklaration hatte ich eigentlich auch. Wahrscheinlich lags daran, dass
OtherClassim Namensraum eine Ebene höher war. Mit expliziter Namensraum-Qualifizierung beimfriendgehts nun...Aber ja, mit
friends und Templates hatte ich auch schon mühsame Auseinandersetzungen. Gerade bei Funktionen...