Template Parameter ist ein Template



  • 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