Implizite Konvertierungen (Split aus "Operatoren für eigene Double Klasse")



  • 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 von std::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 Schreibweise MyClass a = "hallo" gleichbedeutend ist mit MyClass 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 von std::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 -> MyClass geschieht implizit (schliesslich ist der String kein direktes Konstruktorargument und wird auch nicht gecastet). Da der Konvertierungskonstruktor nicht explicit ist, spielt das aber keine Rolle.

    hustbaer schrieb:

    Ich dachte dass das gleichbedeutend ist mit a("hallo") .

    Bei MyClass a = "hallo"; darf der Konstruktor nicht explicit sein. 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 nicht explicit sein. 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 gleichem T Freundschaft 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 gleichem T Freundschaft 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 was compiliert - ob das dann laut Standard OK ist oder nicht ... pfuh 😕



  • Die Vorwärtsdeklaration hatte ich eigentlich auch. Wahrscheinlich lags daran, dass OtherClass im Namensraum eine Ebene höher war. Mit expliziter Namensraum-Qualifizierung beim friend gehts nun...

    Aber ja, mit friend s und Templates hatte ich auch schon mühsame Auseinandersetzungen. Gerade bei Funktionen...


Anmelden zum Antworten