Template Parameter ist ein Template



  • scheinbar klappt der static_cast trick nicht mit virtuellen basisklassen.



  • Interessante Idee mit dem static_cast Trick,
    aber fehlt an dieser Stelle nicht einfach nen virtual?

    Derived& down()
            {
                 return static_cast<Derived&>(*this);
            }
    

    Ansonsten wird bei einem Object, das von einem Derived-Object abgeleitet wird
    und selbst wieder Derived besitzt doch die Methode des Basis-Objektes
    verwendet, falls man einen Basis-Zeiger hat und folglich wird der falsche
    cast benutzt. Ein virtual müßte doch (ungetestet) Abhilfe schaffen...

    btw:
    Irgendwie komm ich mir komisch vor, wenn ich mich mit meinen paar Beiträgen
    in die Diskussion zwischen zwei alteingesessenen einbringe.
    Also, wenn ich Mist schreibe: SORRY

    Gruß,
    CSpille



  • Hab es mir nochmal überlegt ^^, das geht wegen der Referenz im
    Rückgabetyp wohl nicht, aber wenn man da nen void* Zeiger zurückliefert?
    Ansonsten erwarteter ja nichts höheres, denn der Rückgabetyp der
    Basis-Klasse wird ja nicht angepaßt.

    EDIT: *argh* dann funct der selbstgeschriebene == Operator ja nicht, sondern
    nur nen Zeiger vergleich.
    Also vergeßt es einfach 🙄

    Gruß,
    CSpille



  • CSpille schrieb:

    btw:
    Irgendwie komm ich mir komisch vor, wenn ich mich mit meinen paar Beiträgen
    in die Diskussion zwischen zwei alteingesessenen einbringe.
    Also, wenn ich Mist schreibe: SORRY

    He, schäm dich niemals, wenn du versuchst deine gedanken für eine problemlösung beizusteuern. Selbst wenn es nicht stimmen sollte, ist das immernoch bei weitem besser, als einen guten gedanken zu verwerfen!

    So, mal eine Lösung die ich mir mal so überlegt hab:

    //da der virtual ansatz nicht klappt, muss der Save dann wohl so aussehen:
    template<class Derived,int i>
    class TypeSave
    {
        protected:
            Derived& down()
            {
                 return static_cast<Derived&>(*this);
            }
            const Derived& down()const
            {
                 return static_cast<const Derived&>(*this);
            }
    };
    //was nicht geht, ist dann sowas machen:
    template <typename T>
    struct equitable :public TypeSave<T,1>
    {
    };
    //das ist schlecht erweiterbar, weil alle wissen müssten, welche Zahlen bereits belegt sind.
    
    //Also schlag ich mal die Holzhammer Methode vor
    struct equitable
    {
        public:
            template<typename T,int i>
            struct Operators:public TypeSave<T,i>
            {
                 typedef Operators<T,i> self_t;
    
                 friend bool operator ==(self_t const& a, self_t const& b)
                 {
                      return a.down().equal(b.down());
                 }
                 friend bool operator !=(self_t const& a, self_t const& b)
                 {
                      return not a.down().equal(b.down());
                 }
            };
    };
    //eine zweite fähigkeit zur verdeutlichung
    struct addable
    {
        public:
            template<typename T,int i>
            struct Operators:public TypeSave<T,i>
            {
                 typedef Operators<T,i> self_t;
    
                 friend T operator +(self_t const& a, self_t const& b)
                 {
                      T temp=a.down();
                      temp+=b.down();
                      return temp;
                 }
            };
    };
    //ein default wert
    struct Default
    {
        public:
            template<typename T,int i>
            struct Operators{};
    };
    //helferklasse die die Fähigkeiten zusammenfasst. der erste Parameter ist der name von Derived, so kann man sich das tippen bei den Operatoren sparen
    template<class Type,class T1=Default,class T2=Default,class T3=Default,class T4=Default>
    struct Caps:public T1::template Operators<Type,1>,public T2::template Operators<Type,2>,
                public T3::template Operators<Type,3>,public T4::template Operators<Type,4>
    {};
    
    class Test:public Caps<Test,addable,equitable>
    {
        public:bool equal(const Test&b)const
        {
            return true;
        }
        Test& operator+=(const Test& other)
        {
            return *this;
        }
    };
    

  • Mod

    Den Wald vor lauter Bäumen nicht gesehen? Die Mehrfachvererbung ist nur bei einem direkten Cast in den Typ des vollständigen Objektes mehrdeutig.

    // static_cast ist bei nicht-virtueller Mehrfachvererbung mehrdeutig, weil nicht klar ist,
    // mit welchem Subobjekt wir es zu tun haben (und aus einem ähnlichen Grunde ist staic_cast
    // bei virtueller Vererbung nicht nutzbar - für einen Downcast aus einer virtuellen Basisklasse
    // müssen wir den Typ des vollständigen Objekts kennen)
    template<class Derived,template<typename> class Concept>
    class TypeSave
    {
    protected:
        Derived& down()
        {
            return static_cast<Derived&>(static_cast<Concept<Derived>&>(*this));
        }
        const Derived& down()const
        {
            return static_cast<const Derived&>(static_cast<const Concept<Derived>&>(*this));
        }
    };
    
    template <typename T>
    struct equitable : public TypeSave<T,equitable>
    {
        friend bool operator ==(const equitable& a, const equitable& b)
        {
            return a.down().equal(b.down());
        }
        friend bool operator !=(const equitable& a, const equitable& b)
        {
            return not a.down().equal(b.down());
        }
    };
    
    template <typename T>
    struct addable : public TypeSave<T,addable>
    {
        friend T operator +(const addable& a, const addable& b)
        {
            T temp=a.down();
            temp+=b.down();
            return temp;
        }
    };
    
    //Defaultwerte
    template<typename T> struct Default1 {};
    template<typename T> struct Default2 {};
    template<typename T> struct Default3 {};
    template<typename T> struct Default4 {};
    template<typename T> struct Default5 {};
    template<typename T> struct Default6 {};
    //helferklasse die die Fähigkeiten zusammenfasst. der erste Parameter ist der name von Derived, so kann man sich das tippen bei den Operatoren sparen
    template<class Type,template<typename> class T1=Default1,template<typename> class T2=Default2,template<typename> class T3=Default3,
                        template<typename> class T4=Default4,template<typename> class T5=Default5,template<typename> class T6=Default6>
    struct Caps: T1<Type>, T2<Type>, T3<Type>, T4<Type>, T5<Type>, T6<Type>
    {};
    
    class Test:public Caps<Test,addable,equitable>
    {
        public:bool equal(const Test&b)const
        {
            return true;
        }
        Test& operator+=(const Test& other)
        {
            return *this;
        }
    };
    


  • Ich hab da mal zwei Links für euch:

    • Boost.Preprocessor, sehr gut für sachen wie die 6 Default-Klassen (BOOST_PP_REPEAT)
    • Fragments, ein Projekt von rüdiger und mir, das dem Ganzen hier doch verdächtlich ähnlich ist (verdammt unfertig)


  • camper schrieb:

    Den Wald vor lauter Bäumen nicht gesehen? Die Mehrfachvererbung ist nur bei einem direkten Cast in den Typ des vollständigen Objektes mehrdeutig.

    Nene, geht schon. Der Wald ist hier, und er besteht aus vielen schönen Bäumen ;). Deine Lösung um die mehrdeutigkeit beim casten auflösen löst das Problem schon in der weise, dass es garnichtmehr auftreten kann. durch den 2. template parameter im TypeSave ist jede Basisklasse automatisch eindeutig. Nichtsdestotrotz ist der zweite template parameter bei dir wesentlich cooler als bei mir 😃

    Das Problem ist, dass ich nicht weis, ob static_cast die addresse eines zeigers/einer referenz ändern kann. Wenn nicht, dann setzen wir hier auf compilerspezifisches verhalten weil wir nicht garantieren können, dass alle Basisklassen auf allen Compilern die größe 0 haben und damit alle dieselbe addresse haben.

    @Mr.N was wir wiegentlich grad machen geht stark in die richtung boost::operators.


  • Mod

    otze schrieb:

    Deine Lösung um die mehrdeutigkeit beim casten auflösen löst das Problem schon in der weise, dass es garnichtmehr auftreten kann. durch den 2. template parameter im TypeSave ist jede Basisklasse automatisch eindeutig.

    Stimt. Ursprünglich wollte ich das noch anders machen:

    template<class Derived>
    class TypeSave
    {
    protected:
        template<template<typename> class Concept>
        static Derived& down(Concept<Derived>& x)
        {
            return static_cast<Derived&>(x);
        }
        template<template<typename> class Concept>
        const Derived& down(const Concept<Derived>& x)
        {
            return static_cast<const Derived&>(x);
        }
    };
    template <typename T>
    struct equitable : public TypeSave<T>
    {
        friend bool operator ==(const equitable& a, const equitable& b)
        {
            return a.down(a).equal(b.down(b));
        }
        friend bool operator !=(const equitable& a, const equitable& b)
        {
            return not a.down(a).equal(b.down(b));
        }
    };
    

    Ist aber auch nicht wirklich besser.

    Das Problem ist, dass ich nicht weis, ob static_cast die addresse eines zeigers/einer referenz ändern kann. Wenn nicht, dann setzen wir hier auf compilerspezifisches verhalten weil wir nicht garantieren können, dass alle Basisklassen auf allen Compilern die größe 0 haben und damit alle dieselbe addresse haben.

    Inwiefern ist das ein Problem? static_cast kann die Adresse ändern, wo nötig, wenn die entsprechenden Informationen zur Verfügung stehen (bei virtueller Vererbnung nicht der Fall, da es keine Möglichkeit gibt, dem Compiler zu sagen, dass der Zieltyp der Typ des kompletten Objekts ist). Bei Mehrfachvererbung mit nichtleeren Basisklassen ist das ganz zwangsläufig der Fall. Andererseits funktioniert die EBO in der Regel überhaupt nicht mit Mehrfachvererbung (denn zwei verschiedene Objekte (die nicht jeweils Teil des anderen sind) können während ihrer Lebenszeit nicht die gleiche Adresse haben), was generell ein Problem bei dieser Art von Komposition ist. Das kriegt man weg, wenn man in Einfachvererbung transformiert, dann betsteht wieder ein Problem, dass die Anzahl der Templateinstantiierungen explodiert. Ist alles nicht so ganz das Wahre.



  • Ich denke, einfachvererbung ist wirklich der beste Weg:

    template<class Derive>
    class TypeSave
    {
        protected:
            typedef Derive Derived;
            Derived& down()
            {
                 return static_cast<Derived&>(*this);
            }
            const Derived& down()const
            {
                 return static_cast<const Derived&>(*this);
            }
    };
    template<class Base>
    struct Default:public Base{};
    
    //geht sicherlich schöner
    template<
        class Type,
        template<class>class T1=Default,
        template<class>class T2=Default,
        template<class>class T3=Default,
        template<class>class T4=Default,
        template<class>class T5=Default,
        template<class>class T6=Default
    >
    class Caps:public T1<T2<T3<T4<T5<T6<TypeSave<Type> > > > > > >{};
    
    template <class Base>
    struct equitable : public Base
    {
        friend bool operator ==(const equitable& a, const equitable& b)
        {
            return a.down().equal(b.down());
        }
        friend bool operator !=(const equitable& a, const equitable& b)
        {
            return not a.down().equal(b.down());
        }
    };
    template<class Base>
    struct addable:public Base
    {
    
        friend typename Base::Derived operator +(const addable& a,const addable& b)
        {
            typename Base::Derived temp=a.down();
            temp+=b.down();
            return temp;
        }
    };
    
    class Test:public Caps<Test,addable,equitable>
    {
        public:bool equal(const Test&b)const
        {
            return true;
        }
        Test& operator+=(const Test& other)
        {
            return *this;
        }
    };
    


  • otze schrieb:

    @Mr.N was wir wiegentlich grad machen geht stark in die richtung boost::operators.

    Mag sein. Mit dem Einfachvererbungszeug seid ihr jetzt allerdings bei der Architektur von Fragments angelangt, wobei fragments (noch) kein down() hat. Nehmen wir mal an, es gäbe down (und derived), dann sähe addable etwa so aus:

    struct addable {
      // ich benutze die concept+require maschinerie mal _nicht_
      typedef boost::mpl::vector0<> concept;
    
      template<typename Before, typename>
      struct fragment : Before {
        typename Before::derived operator+(fragment const &o) const {
          typename Before::derived tmp(*this);
          tmp += o;
          return tmp;
        }
      };
    };
    
    combiner<addable, ...> x, y, z;
    ...
    x = y + z;
    


  • otze schrieb:

    @Mr.N was wir wiegentlich grad machen geht stark in die richtung boost::operators.

    Das war eigentlich eher weniger mein ursprünglicher Sinn. Wenn schon, dann müsste man es umgekehrt sagen: boost::operators geht in Richtung des Haskell-Frameworks. Das "besondere" an diesem Code (im Gegensatz zu b:🤡 ist ja, dass die Klasse Equitable gewissermaßen den C++0x-Begriff der Concept Maps vorausnimmt, indem *beide* Operatoren ('==' und '!=') sowohl deklariert als auch implementiert werden.

    -- Letztendlich ging es mir dabei aber um ein Proof of Concept. Ich arbeite (schon seit geraumer Zeit) an einer eigenen, generischen Programmiersprache, die allerdings vom Ansatz her kaum etwas mit C++ gemein hat. Leider ist die Typ-Semantik dieser Programmiersprache verdammt komplex und ich war mir nicht hundertprozentig sicher, ob das überhaupt umsetzbar wäre (ich weiß, mathematische Beweisbarkeit ... aber so'nen Beweis zusammenzubasteln ist auch nicht so einfach). Dieser Code hier ist aber letztendlich der Beweis, dass es klappt. Die Klasse 'TypeSafe' ist dabei aber ziemlich egal, das ist ja letztendlich nur ein kleines Hilfsmittel, um die Syntax des Casts zu verschönern.

    Trotzdem ist es auf jeden Fall interessant zu sehen, auf was für Ideen ihr da so kommt.



  • was hast du dir da denn bei der sprache vorgestellt? kann man sich vielleicht ein beispiel anschauen, wie das aussehen soll? 🙂



  • Ach otze, gerade wo du schon hier bist :). Warst du nicht der jenige der ein GUI-Toolkit am entwickeln war, wenn ja wie siehts damit aus ?



  • otze schrieb:

    was hast du dir da denn bei der sprache vorgestellt? kann man sich vielleicht ein beispiel anschauen, wie das aussehen soll? 🙂

    Oh herrjemine. Ich tyrannisiere schon eine andere Community damit. 😉

    Die Sprache hat den Codenamen "Caliph" und soll halt im wesentlichen eine Abstraktion für generische Programmierung *ohne* Laufzeit-Overhead bieten. Dementsprechend also ähnlich wie C++. Der große Unterschied zu C++ ist, neben der fundamental anderen Syntax, die Erweiterbarkeit der Klassen sowie die Namensbindung. Beispielsweise würde der Aufruf 'Length(x)' eine statische Methode 'Length' für das Objekt 'x' aufrufen statt einer freien Funktion. 'x.Length()' wäre dementsprechend eine nicht-statische Methode.

    Momentan habe ich kein Material zu Caliph online. Ich arbeite gerade an ner FAQ, die dauert aber wohl noch ein Weilchen aufgrund von Hardware-Schwierigkeiten.

    Der Beispielcode könnte z.B. so aussehen:

    typeclass Equitable
        operator static =(a as Equitable, b as Equitable) as bool := not a != b
        operator static !=(a as Equitable, b as Equitable) as bool := not a = b
    end
    
    class String
        var private value as Array[Char]
        # ... Zeugs ...
        property public get Length as int := value.Length
    end
    
    instance String of Equitable
        operator static =(a as String, b as String) as bool
            return
                a.Length = b.Length and
                Zip(a, b, lambda(x, y) := x = y).Fold(true, lambda(x, y) := x and y)
        end
    end
    

    Drei Besonderheiten:

    - 'typeclass' spezifiziert ein Verhalten für eine Klasse. Eben ein Konzept.
    - 'instance' sorgt dafür, dass eine Klasse ein Verhalten übernimmt.
    - Zeile 13 ff. zeigen den funktionalen Aspekt der Sprache. Ginge natürlich wesentlich effizienter, zumal Arrays sowieso auch 'Equitable' wären und man den Aufruf also nur zu delegieren bräuchte. 😉 (Wobei ... ein *richtig* guter Compiler mit verzögerter Auswertung könnte das optimieren.)

    Eine kleine Besonderheit offenbart sich auch in der For-Schleife, die stets folgendermaßen aussieht (es gibt nix à la 'for i from ... to ...'):

    For x in y
    

    x ist hierbei ein Iterator und y ein Bereich -- das kann ein Container sein, oder eben auch ein on-the-fly generierter Zahlenbereich. Anders als in .NET oder Java wird hier aber nicht diese 'hasnext'-Methode verwendet sondern es werden die aus C++ bekannten Iteratoren implementiert. Die Sprache verfügt auch über Zeiger, allerdings handelt es sich hierbei nur um eine spezielle Implementierung des RandomAccess-Iterators.

    Leider klaut das C++-Standardkommittee schamlos meine Ideen und mit jeder weiteren Veröffentlichung zu C++0x schrumpf der Vorsprung von Caliph. 😞 Beispielsweise waren Concepts nicht genug, nein, sie mussten gleich Concept maps einführen -- und somit prinzipiell existierende Klassen erweiterbar machen. Schon war ein großer Trumpf von Caliph dahin.



  • KasF schrieb:

    Ach otze, gerade wo du schon hier bist :). Warst du nicht der jenige der ein GUI-Toolkit am entwickeln war, wenn ja wie siehts damit aus ?

    Nein, das war ich (wenn du GOTT meinst). Ist zur Zeit nicht aktiv.

    @Konrad Rudolph: Zum Thema "Equitable": Frag Leo - vielleicht wäre eine andere Bezeichnung besser. 😉 - Ich habe "equality_comparable" genommen, aber so toll ist das natürlich auch nicht.



  • Mr. N schrieb:

    @Konrad Rudolph: Zum Thema "Equitable": Frag Leo - vielleicht wäre eine andere Bezeichnung besser. 😉

    Offensichtlich. 🙂

    'EqualityComparable' ist mir aber ehrlich gesagt für ein solch ubiquitäres Konzept zu lang (auch bei 'RandomAccessIterator' bekomme ich jedes Mal ne Krise aber da ist mir auch noch nix kürzeres eingefallen). Eventuell mache ich's wie in Haskell und verwende 'Eq', 'Ord' etc. -- Abkürzungen finde ich eigentlich aber auch nicht so besonders toll.


Anmelden zum Antworten