Lokale Kopie von Base Klasse anlegen?



  • happystudent schrieb:

    Naja, ich muss den immer gleichen Code immer wieder in jede Derived Klasse reinkopieren. Komplett redundant und trivial.

    Nein, es ist nicht der selbe Code. Er unterscheidet sich im Namen der abgeleiteten Klasse. Wäre es der selbe Code, würde ja auch ein parameterloses Makro reichen, nen?

    Ansonsten: Siehe CRTP Lösung oben.

    happystudent schrieb:

    Mich interessiert das einfach.

    Dann denk drüber nach.

    happystudent schrieb:

    Gut, die Syntax war ein bisschen unglücklich gewählt, aber sowas:

    struct Base
    {
      typedef std::get_child<Base>::type child_type; // So im type traits Stil halt
    
      virtual int out(int in) = 0;
      child_type* create(){ 
        child_type* e = new child_type();
        return e;
      }
    };
     
    struct Derived1 : Base
    {
      // bekommt jetzt ein create implementiert
      int out(int in){ return in; }
    };
    

    Wenn kein Child vorhanden halt Compilefehler?

    struct Base
    {
      typedef std::get_child<Base>::type child_type;
    
      virtual int out(int in) = 0;
      child_type* create(){ 
        child_type* e = new child_type();
        return e;
      }
    };
    
    struct Derived1 : Base
    {
      int out(int in){ return in; }
    };
    
    struct Derived2 : Base
    {
      int out(int in){ return in; }
    };
    
    Base::child_type foo; // welcher typ?
    auto bar = &Base::create; // welcher typ? zeiger auf welche Funktion?
    

    Uswusf.
    Das sind echt so triviale Dinge wo's da gleich hakt, da müsstest du selbst draufkommen.
    Und dann macht es nicht viel Spass dir das zu erklären, weil man halt den Eindruck hat dass du da ne sehr verschwommene Idee hattest die du süss findest, und nun meinst du es müsste gehen. Aber ohne dass du dir mal ein paar Minuten Zeit genommen hast die Sache durchzudenken.



  • hustbaer schrieb:

    Nein, es ist nicht der selbe Code. Er unterscheidet sich im Namen der abgeleiteten Klasse. Wäre es der selbe Code, würde ja auch ein parameterloses Makro reichen, nen?

    Das ist schon klar dass das nicht der selbe Code ist. Aber das ist bei templates ja auch so. Da generiert sich der Compiler ja auch von alleine das zusammen was er braucht und nur so etwas fände ich halt praktisch.

    hustbaer schrieb:

    Ansonsten: Siehe CRTP Lösung oben.

    Ist auch nur (noch) eine andere Schreibweise für das gleiche Problem.

    hustbaer schrieb:

    Uswusf.
    Das sind echt so triviale Dinge wo's da gleich hakt, da müsstest du selbst draufkommen.
    Und dann macht es nicht viel Spass dir das zu erklären, weil man halt den Eindruck hat dass du da ne sehr verschwommene Idee hattest die du süss findest, und nun meinst du es müsste gehen. Aber ohne dass du dir mal ein paar Minuten Zeit genommen hast die Sache durchzudenken.

    Nein, das ist jetzt eine Unterstellung, ich hab mir sehr wohl Zeit genommen. Genauer gesagt denke ich den halben Tag schon darüber nach. Zu deinen Beispielen:

    Base::child_type foo; // Compilefehler: child_type nur im Kontext eines childs verfügbar
    auto bar = &Base::create; // Compilefehler: Base hat kein member create, da dieses nur in childs implementiert wird
    


  • happystudent schrieb:

    hustbaer schrieb:

    Nein, es ist nicht der selbe Code. Er unterscheidet sich im Namen der abgeleiteten Klasse. Wäre es der selbe Code, würde ja auch ein parameterloses Makro reichen, nen?

    Das ist schon klar dass das nicht der selbe Code ist. Aber das ist bei templates ja auch so. Da generiert sich der Compiler ja auch von alleine das zusammen was er braucht und nur so etwas fände ich halt praktisch.

    Du willst doch aber kein Template. Wir bewegen uns im Kreis.

    happystudent schrieb:

    hustbaer schrieb:

    Ansonsten: Siehe CRTP Lösung oben.

    Ist auch nur (noch) eine andere Schreibweise für das gleiche Problem.

    Also willst du das Feature nur, weil es dir nicht gefällt wenn du den Klassennamen der abgeleiteten Klasse an genau einer Stelle wiederholen musst? WTF?

    happystudent schrieb:

    hustbaer schrieb:

    Uswusf.
    Das sind echt so triviale Dinge wo's da gleich hakt, da müsstest du selbst draufkommen.
    Und dann macht es nicht viel Spass dir das zu erklären, weil man halt den Eindruck hat dass du da ne sehr verschwommene Idee hattest die du süss findest, und nun meinst du es müsste gehen. Aber ohne dass du dir mal ein paar Minuten Zeit genommen hast die Sache durchzudenken.

    Nein, das ist jetzt eine Unterstellung, ich hab mir sehr wohl Zeit genommen.

    OK, ich hätte "ich" statt "man" schreiben sollen. Und dann ist es gar keine Unterstellung - ICH habe nämlich den Eindruck.

    happystudent schrieb:

    Base::child_type foo; // Compilefehler: child_type nur im Kontext eines childs verfügbar
    auto bar = &Base::create; // Compilefehler: Base hat kein member create, da dieses nur in childs implementiert wird
    

    Wie willst du create dann über einen Basisklassenzeiger aufrufen, wenn Base kein Member create hat?



  • tl;dr:
    Das Problem aus einem Basisklassenzeiger einen Zeiger zu machen, der auf eine entsprechende Derived-Klasse zeigt, ist schon länger bekannt.
    Gegen den naheliegenden Ansatz - den mit der virtual clone Funktion - sprechen die Nachteile, die du genannt hast: es ist quasi immer dasselbe, man muss es in jede Basisklasse reinschreiben und kann es bei tieferen Hierachien evtl. vergessen.
    Eine andere Methode wäre eine clone-Factory. Die speichert intern eine std::(unordered)_map von dem std::type_index eines Typen und eine entsprechende Funktion, die bei Parameter-Übergabe einen Klon zurückgibt. Man muss sie dann nur bei Programmstart initialisieren.
    In etwa so: http://ideone.com/GLH7h4
    Das ist dann aber langsamer als die andere Variante.
    Die beste Lösung ist es imo, klonen zu vermeiden.



  • happystudent schrieb:

    großbuchstaben schrieb:

    meinst du sowas ?

    Nee, dann muss ich ja bei jedem Aufruf von foo immer das template Argument mit angeben.

    mußt du doch gar nicht:

    #include<iostream>
    
    struct Base
    {
      virtual int out(int in) = 0;
      template<typename T> T* create(){
        T* e = new T();
        return e;
      }
    };
    
    struct Derived1 : Base
    {
      int out(int in){ return in; }
    };
    
    struct Derived2 : Base
    {
      int out(int in){ return 2*in; }
    };
    
    template<typename T> void foo(Base &bar, int in)
    {
      Base* copy2 = bar.create<T>();
      std::cout << copy2->out(in) << '\n';
    }
    
    #define Foo(b, i) foo<decltype(b)>(b, i);
    
    int main(void){
      Derived1 b1;
      Derived2 b2;
      Foo(b1, 3);
      Foo(b2, 3);
      return 0;
    }
    


  • happystudent schrieb:

    Lokale Kopie von Base Klasse anlegen?

    abgesehen von den mehr oder weniger pfiffigen vorschlägen:
    dein vorhaben ist doch unfug.



  • hustbaer schrieb:

    Du willst doch aber kein Template. Wir bewegen uns im Kreis.

    Das war natürlich nur ein Beispiel:

    template <typename T> void foo(T const &t_) { std::cout << t_; }
    
    int main()
    {
        foo(1);
        foo("hallo");
        foo(2.5);
    }
    

    ist auch jeweils ein anderer Code (dank templates), trotzdem muss ich den template Parameter nicht explizit angeben.

    hustbaer schrieb:

    Wie willst du create dann über einen Basisklassenzeiger aufrufen, wenn Base kein Member create hat?

    Ganz einfach, über ein optional anzugebendes template argument. Wie gesagt, es geht mir eigentlich nur um die Idee, wie das von der Sprache dann letztendlich umgesetzt wird ist ja wieder was anderes. Mit typdef war vielleicht blöd, aber so in der Richtung, mit einem neuen template-typ:

    struct Base
    {
    	template <childname T> T* clone() { return new T(); } // childname bezeichnet den abgeleiteten Typ
    };
    
    struct Derived : public Base
    {
    
    };
    
    int main()
    {
    	Derived d;
    	Base *b = d.clone(); // Eindeutig, kann aus dem Kontext hergeleitet werden
    	auto bar = &Base::clone<Derived>; // Nicht eindeutig, Kontext muss explizit angegeben werden
    }
    

    Wäre doch viel besser, oder?

    Nathan schrieb:

    Die beste Lösung ist es imo, klonen zu vermeiden.

    Hm, auch nicht ganz befriedigend aber Danke...

    großbuchstaben schrieb:

    mußt du doch gar nicht:

    Hm, werd ich mal versuchen, kommt schon recht nahe ans Optimum denke ich...



  • Das ist doch alles Quatsch. Ich klinke mich aus.



  • volkard schrieb:

    abgesehen von den mehr oder weniger pfiffigen vorschlägen:
    dein vorhaben ist doch unfug.

    Warum ist es Unfug eine lokale Kopie einer Base Klasse anfertigen zu wollen?

    Jockelx schrieb:

    Mach doch sowas (grob, smartPointer & Co fehlen)

    Also ich hab das mal gemacht und hab dazu noch eine Frage. Das war ja der Code mit rohen Pointern:

    struct Base
    {
    	virtual int output(int input) = 0;
    	virtual Base* clone() const = 0;
    };
    
    struct Derived1 : Base
    {
    	Derived1() : prev_input(0) {}
    
    	int prev_input;
    
    	int output(int input) {
    		prev_input += input;
    		return prev_input;
    	}
    
    	Derived1* clone() const
    	{
    		return new Derived1(*this);
    	}
    };
    
    void foo(Base &bar)
    {
    	Base *clone = bar.clone();
    }
    

    Was auch klappt. Jetzt habe ich zu Smart-Pointern gewechselt und habe das hier:

    struct Base
    {
    	virtual int output(int input) = 0;
    	virtual std::shared_ptr<Base> clone() const = 0;
    };
    
    struct Derived1 : Base
    {
    	Derived1() : prev_input(0) {}
    
    	int prev_input;
    
    	int output(int input) {
    		prev_input += input;
    		return prev_input;
    	}
    
    	std::shared_ptr<Base> clone() const // <- Frage
    	{
    		return std::make_shared<Derived1>(*this);
    	}
    };
    
    void foo(Base &bar)
    {
    	auto clone = bar.clone();
    }
    

    Dabei geht das aber nur wenn ich einen std::shared_ptr<Base> zurückgebe und nicht bei einem std::shared_pointer<Derived1> da smart pointer anscheinend keine kovarianten Rückgabewerte unterstützen.

    Allerdings klappt es trotzdem so wie ich will, daher mal die Frage: Bringt das irgendwelche Probleme mit sich, das so zu machen?



  • happystudent schrieb:

    volkard schrieb:

    abgesehen von den mehr oder weniger pfiffigen vorschlägen:
    dein vorhaben ist doch unfug.

    Warum ist es Unfug eine lokale Kopie einer Base Klasse anfertigen zu wollen?

    Um aus einem Hund eine Katze zu machen, indem man den gemeinsamen Säugetier-Kram rauskopiert und ganz vorne Schnurrhaare dranklebt? Das ist so abwegig, daß ich gar nicht verstehe, daß Du so viele Antworten bekommen hast.

    Mach mal ein Beispiel nicht nur mit foo und bar und ganz abstrakten Templates, wo dein Vorhaben sinnvoll ist und nicht geradewegs auf Abwege und umkippende Software führt.

    Ich muss mir jetzt nochmal http://www.c-plusplus.net/forum/75672-full durchlesen, damit Du mich nicht verwirrst.



  • volkard schrieb:

    Um aus einem Hund eine Katze zu machen, indem man den gemeinsamen Säugetier-Kram rauskopiert und ganz vorne Schnurrhaare dranklebt? Das ist so abwegig, daß ich gar nicht verstehe, daß Du so viele Antworten bekommen hast.

    Mach mal ein Beispiel nicht nur mit foo und bar und ganz abstrakten Templates, wo dein Vorhaben sinnvoll ist und nicht geradewegs auf Abwege und umkippende Software führt.

    Ich muss mir jetzt nochmal http://www.c-plusplus.net/forum/75672-full durchlesen, damit Du mich nicht verwirrst.

    Ok, hier ein weniger abstraktes Beispiel:

    struct Bauelement
    {
    	virtual int output(int input) = 0;
    	virtual std::shared_ptr<Bauelement> clone() const = 0;
    };
    
    struct Heizplatte : public Bauelement
    {
    	Heizplatte() : prev_strom(0) {}
    
    	int prev_strom;
    
    	int output(int strom) {
    		prev_strom += strom; // Stark vereinfacht
    		return prev_strom;
    	}
    
    	std::shared_ptr<Bauelement> clone() const
    	{
    		return std::make_shared<Heizplatte>(*this);
    	}
    };
    
    void vergleiche_zwei_bauelemente(Bauelement &b1_, Bauelement &b2_)
    {
    	auto b1 = b1_.clone(), b2 = b2_.clone();
    	// Hier müssen wieder outputs berechnet werden, die Bauelemente werden anhand ihres outputs verglichen
    	// Dank der clone-Funktion bleiben die ursprünglichen Bauelemente unverändert, was auch so sein soll
    	b1.get()->output(1);
    	b2.get()->output(5); // Vergleiche die outputs
    }
    
    int main()
    {
    
    	Heizplatte h1;
    	Heizplatte h2;
    
    	// Stelle Berechnungen an, die den Speicher der Bauelemente "aufladen"
    	h1.output(1);
    	h2.output(3);
    
    	// Vergleiche die Bauelemente; durch den Vergleich sollen diese aber _nicht_ verändert werden
    	vergleiche_zwei_bauelemente(h1, h2);
    }
    

    Deinen verlinkten Artikel habe ich gelesen, aber die dort beschriebene Problematik passt nicht auf mein Problem: Heizplatte ist ein Bauelement und besitzt keines. Es kann zwar auch (beliebig viele) besitzen, von außen betrachtet verhält es sich aber wie jedes andere Bauelement auch.



  • happystudent schrieb:

    volkard schrieb:

    Um aus einem Hund eine Katze zu machen, indem man den gemeinsamen Säugetier-Kram rauskopiert und ganz vorne Schnurrhaare dranklebt? Das ist so abwegig, daß ich gar nicht verstehe, daß Du so viele Antworten bekommen hast.

    Mach mal ein Beispiel nicht nur mit foo und bar und ganz abstrakten Templates, wo dein Vorhaben sinnvoll ist und nicht geradewegs auf Abwege und umkippende Software führt.

    Ich muss mir jetzt nochmal http://www.c-plusplus.net/forum/75672-full durchlesen, damit Du mich nicht verwirrst.

    Ok, hier ein weniger abstraktes Beispiel:

    struct Bauelement
    {
    	virtual int output(int input) = 0;
    	virtual std::shared_ptr<Bauelement> clone() const = 0;
    };
    
    struct Heizplatte : public Bauelement
    {
    	Heizplatte() : prev_strom(0) {}
    
    	int prev_strom;
    
    	int output(int strom) {
    		prev_strom += strom; // Stark vereinfacht
    		return prev_strom;
    	}
    
    	std::shared_ptr<Bauelement> clone() const
    	{
    		return std::make_shared<Heizplatte>(*this);
    	}
    };
    
    void vergleiche_zwei_bauelemente(Bauelement &b1_, Bauelement &b2_)
    {
    	auto b1 = b1_.clone(), b2 = b2_.clone();
    	// Hier müssen wieder outputs berechnet werden, die Bauelemente werden anhand ihres outputs verglichen
    	// Dank der clone-Funktion bleiben die ursprünglichen Bauelemente unverändert, was auch so sein soll
    	b1.get()->output(1);
    	b2.get()->output(5); // Vergleiche die outputs
    }
    
    int main()
    {
    	
    	Heizplatte h1;
    	Heizplatte h2;
    
    	// Stelle Berechnungen an, die den Speicher der Bauelemente "aufladen"
    	h1.output(1);
    	h2.output(3);
    
    	// Vergleiche die Bauelemente; durch den Vergleich sollen diese aber _nicht_ verändert werden
    	vergleiche_zwei_bauelemente(h1, h2);
    }
    

    Deinen verlinkten Artikel habe ich gelesen, aber die dort beschriebene Problematik passt nicht auf mein Problem: Heizplatte ist ein Bauelement und besitzt keines. Es kann zwar auch (beliebig viele) besitzen, von außen betrachtet verhält es sich aber wie jedes andere Bauelement auch.

    Dann stimmt doch das Vergleichen nicht oder fehlt.
    Bauelement kopieren, obwohl sie leer ist? Kopie einer abstrakten Basisklasse?
    Also es geht nur drum, zwei Bauelemente vergleichen zu können, indem Du ihnen einen Eingangssignalverlauf schickst und sie gleich drauf reagieren sollen? Das wäre dann doch die Schnittstelle?
    Mir scheint, Du trennt nicht zwischen Bauelemetschablone(Klasse) und Bauelement(Instanz). "aufladen" wäre dann Instanziieren. Zwei Objekte sind gleich, wenn sie von der selben Klasse sind und ihre Attribute gleich sind, reicht das nicht? An den Speicher des Verzögerers, hier nur "int prev_strom;" willste ran? Ist aber nicht "lokale Kopie von Base Klasse anlegen".
    Sollte das Bauelement Bauelementspeicher haben, den man einfrieren und rumvertauschen kann?



  • volkard schrieb:

    Dann stimmt doch das Vergleichen nicht oder fehlt.

    Ja, das ist so nicht vollständig, sollte nur der Verdeutlichung dienen.

    volkard schrieb:

    Bauelement kopieren, obwohl sie leer ist? Kopie einer abstrakten Basisklasse?

    Wieso leer? Die sind nicht leer, bevor ich die vergleichs-Funktion aufrufe habe ich doch bereits geheizt (mittels der output Funktion).

    volkard schrieb:

    Also es geht nur drum, zwei Bauelemente vergleichen zu können, indem Du ihnen einen Eingangssignalverlauf schickst und sie gleich drauf reagieren sollen? Das wäre dann doch die Schnittstelle?
    Mir scheint, Du trennt nicht zwischen Bauelemetschablone(Klasse) und Bauelement(Instanz). "aufladen" wäre dann Instanziieren.

    Nein, aufgeladen bzw. entladen werden die Bauelemente durch Benutzung. Es geht letztendlich um die Vorhersage von Lebensdauer der Bauelemente, daher sieht das Ganze ungefähr so aus:

    while (true) {
    	h1.output(1);
    	h2.output(3);
    
    	// Nach dem Vergleich sollen die Bauelemente im gleichen Zustand sein wie davor
    	vergleiche_zwei_bauelemente(h1, h2);
    
    	if (irgendeine_bedingung)
    		break;
    }
    

    volkard schrieb:

    Zwei Objekte sind gleich, wenn sie von der selben Klasse sind und ihre Attribute gleich sind, reicht das nicht?

    Es geht nicht darum ob zwei Bauelemente gleich sind, sondern ob ihr Verhalten gleich ist. Zwei Heizplatten etwa können völlig unterschiedlich gebaut sein und auch völlig unterschiedliche Attribute haben und trotzdem gleiches Input/Output Verhalten aufweisen (und umgekehrt).

    volkard schrieb:

    An den Speicher des Verzögerers, hier nur "int prev_strom;" willste ran? Ist aber nicht "lokale Kopie von Base Klasse anlegen".
    Sollte das Bauelement Bauelementspeicher haben, den man einfrieren und rumvertauschen kann?

    Ne, an den will ich eben nicht ran. Ich hab irgendein Bauelement, von dem ich nur weiß dass ich ihm einen Strom geben kann und es mir einen Output zurückgibt. Für dieses Bauelement will ich jetzt berechnungen anstellen die seinen internen State nur lokal verändern, nämlich nur für diese lokalen Berechnungen (hier in vergleiche_zwei_bauelemente). Danach will ich weitere Berechnungen mit dem gleichen Bauelement und dessen ursprünglichem Status anstellen können.



  • happystudent schrieb:

    Was auch klappt. Jetzt habe ich zu Smart-Pointern gewechselt und habe das hier:
    (...)
    Dabei geht das aber nur wenn ich einen std::shared_ptr<Base> zurückgebe und nicht bei einem std::shared_pointer<Derived1> da smart pointer anscheinend keine kovarianten Rückgabewerte unterstützen.

    Richtig.

    Auch dabei hilft CRTP (+ non-virtual-interface):
    EDIT: blödsinnige Implementierung korrigiert (siehe Kommentare in clone())

    class Thing
    {
    public:
        std::shared_ptr<Thing> clone() const
        {
            return clone_impl();
        }
    
    private:
        virtual std::shared_ptr<Thing> clone_impl() const = 0;
    };
    
    template <class Derived, class Base> 
    class ThingBase : public Base
    {
    public:
        // Wir überdecken die clone() Funktion der Basisklasse, weil's praktisch ist wenn wir std::shared_ptr<Derived> zurückgeben können
        std::shared_ptr<Derived> clone() const
        {
            // Wir müssen hier aber auf jeden Fall auch clone_impl aufrufen,
            // damit Aufrufer die z.B. Derived::clone() auf ein MoreDerived Objekt
            // aufrufen auch wirklich einen MoreDerived Klon bekommen!
            std::shared_ptr<Thing> const ptr = clone_impl();
            if (!ptr) // Sollte bei einer clone() Funktion nicht passieren, aber wir nur um zu zeigen wie man es z.B. bei einer "try_create" Funktion machen würde...
                return std::shared_ptr<Derived>();
            std::shared_ptr<Derived> const derivedPtr = std::dynamic_pointer_cast<Derived>(ptr);
            if (!derivedPtr) // Ist ein WTF wenn clone_impl() keinen passenden Typ zurückgibt, aber wir sollten es trotzdem checken
                throw std::bad_cast();
            return derivedPtr;
        }
    
    private:
        // Wir überschreiben die clone_impl() Funktion der Basisklasse, damit clone() der Basisklasse korrekt funktioniert
        virtual std::shared_ptr<Thing> clone_impl() const
        {
            std::shared_ptr<Thing> ptr(new Derived(*static_cast<Derived const*>(this)));
            return ptr;
        }
    }; 
    
    // Abgeleitete Klassen
    
    class DerivedThing : public ThingBase<DerivedThing, Thing> 
    { 
    };
    
    class MoreDerivedThing : public ThingBase<MoreDerivedThing, DerivedThing> 
    { 
    };
    
    class AnotherDerivedThing : public ThingBase<AnotherDerivedThing, Thing> 
    { 
    };
    
    // ...
    

    Bzw. mit nem Makro kannst du es natürlich genau so machen.
    Der Vorteil der Makro-Variante ist z.B. dass es dir die Klassendiagramme in automatisch generierten Dokumentationen (Doxygen, ...) nicht mit lauter Zwischenklassen vollsaut.

    happystudent schrieb:

    Allerdings klappt es trotzdem so wie ich will, daher mal die Frage: Bringt das irgendwelche Probleme mit sich, das so zu machen?

    Ich sehe kein Problem.



  • hustbaer schrieb:

    Richtig.

    Auch dabei hilft CRTP (+ non-virtual-interface):

    Das non-virtual-interface sieht interessant aus, kannte ich noch nicht... Btw, gibts eigentlich einen Grund dafür dass kovarianten Rückgabewerte nur von rohen Pointern unterstützt werden?

    hustbaer schrieb:

    Ich sehe kein Problem.

    Das ist gut zu hören 👍

    Dann werd ich mal ein bisschen weiter experimentieren 😃



  • happystudent schrieb:

    hustbaer schrieb:

    Richtig.

    Auch dabei hilft CRTP (+ non-virtual-interface):

    Das non-virtual-interface sieht interessant aus, kannte ich noch nicht... Btw, gibts eigentlich einen Grund dafür dass kovarianten Rückgabewerte nur von rohen Pointern unterstützt werden?

    Das ist eine Art Hack im Typsystem, der eben nur für rohe Pointer ausgelegt ist.



  • happystudent schrieb:

    Das non-virtual-interface sieht interessant aus, kannte ich noch nicht...

    Dann solltest du unbedingt den Artikel von Herb Sutter zu dem Them lesen - einer der C++ "Standardartikel" die mMn. jeder kennen sollte:
    http://www.gotw.ca/publications/mill18.htm

    happystudent schrieb:

    Btw, gibts eigentlich einen Grund dafür dass kovarianten Rückgabewerte nur von rohen Pointern unterstützt werden?

    Grund dafür gibt es ziemlich sicher, aber ich kann ihn dir nicht sagen.
    Ich kann höchstens vermuten...

    z.B. dass bei der Standardisierung entschieden wurde dass der Aufwand nicht dafür steht. Das war ja damals 1998, da waren die Compiler noch LANGE nicht so weit wie heute.

    Oder sie wollten vermeiden dass dabei userdefinierter Code (=der Conversion-Konstruktor) ausgeführt werden muss. Es muss dazu ja vom Compiler eine "Zwischenfunktion" erstellt werden die den originalen Returnwert hat und dann die Funktion des Users aufruft.
    In dieser "Zwischenfunktion" würde dann der Conversion-Konstruktor ausgeführt.
    Nicht dass das technisch ein Problem wäre, aber vielleicht meinten sie einfach dass das "komisch" wäre.

    Der Upcast eines rohen Zeigers ist dagegen trivial und noexcept und erfordert vor allem keinen userdefinierter Code.

    ps: Jetzt wo ich die Antwort von Nathan lese...
    Bei userdefinierten Konvertierungen kann man ja nicht zwischen Covarianz und dem viel allgemeineren Konzept "implizite Konvertierbarkeit" unterscheiden.
    DAS wird vermutlich der Grund gewesen sein.
    Also dass das Committee es nicht richtig fand das allgemein für konvertierbare Typen zu erlauben.

    ps2:

    Nathan schrieb:

    Das ist eine Art Hack im Typsystem, der eben nur für rohe Pointer ausgelegt ist.

    So kann man das mMn. auch nicht sagen. Das Typsystem wird dadurch ja nicht modifiziert. Die dazu nötigen Änderungen im Standard sollten sich auf die Stellen beschränken wo virtuelle Funktionen beschreiben werden. Kann mir nicht vorstellen dass dazu Änderrungen im Typsystem nötig wären.



  • hustbaer schrieb:

    ps2:

    Nathan schrieb:

    Das ist eine Art Hack im Typsystem, der eben nur für rohe Pointer ausgelegt ist.

    So kann man das mMn. auch nicht sagen. Das Typsystem wird dadurch ja nicht modifiziert. Die dazu nötigen Änderungen im Standard sollten sich auf die Stellen beschränken wo virtuelle Funktionen beschreiben werden. Kann mir nicht vorstellen dass dazu Änderrungen im Typsystem nötig wären.

    Ich meinte lediglich, dass man dadurch eine virtuelle Funktion mit andere Signatur überschreibt, als eigentlich da ist, was sonst nicht geht. Deswegen ein "Hack" im Typsystem, da der return type auf einmal auch einen anderen Wert haben kann.



  • Je näher ich darüber nachdenke, desto "hackiger" kommt es mir auch vor. Ob man jetzt "im Typsystem" sagen will oder nicht... OK, ganz unpassend ist es nicht.

    Was ich z.B. nicht wusste (grad ausprobiert), aber irgendwie vernünftig ist, ist...
    Dass man in einer Klasse die von "Derived" abgeleitet ist dann auch mindestens einen "Derived*" zurückgeben muss, wenn "Derived" den Returntyp covariant verändert hat.

    Also

    struct Base
    {
    	virtual Base* clone() = 0;
    };
    
    struct Derived : Base
    {
    	virtual Derived* clone()
    	{
    		return new Derived();
    	}
    };
    
    struct MoreDerived : Derived
    {
    	virtual Derived* clone() // Muss MINDESTENS Derived* sein, weil Derived::clone den Typ auf Derived* "runtergezogen" hat
    	{
    		return new MoreDerived();
    	}
    };
    

    Und das ist schon irgendwie "hackig".

    Wobei mir auf die Schnelle auch keine bessere Lösung einfällt.



  • Argh. Das passiert wenn man schnell was hinschreibt und nicht genug darüber nachdenkt.
    Meine tolle "non-virtual-interface" Implementierung war natürlich total kaputt.
    Hab's korrigiert.


Anmelden zum Antworten