Klassentemplate: Nur einzelne Methoden spezialisieren


  • Mod

    geloescht schrieb:

    ah, danke, darauf bin ich nicht gekommen. Wird zwar etwas mehr Code, aber sehr viel weniger als wenn ich wirklich alles doppelt und dreifach schreiben würde.
    Dann hab ich noch ne Frage, ich hab irgendwo in der Boost-Doku, genauergesagt hier http://boost.org/libs/utility/operators.htm#chaining (ich hab mit boost gerade erst angefangen 🙄 aber es erleichtert doch einiges) gelesen, dass bei manchen Compilern bei Ableitung von Klassen ohne Member die Objektgröße anwächst. Da auch mal große Mengen an Matrizen und Vektoren gespeichert werden sollen wäre das nicht sehr schön. Ist das wirklich so?

    Wenn nicht, stände diese Behauptung dort nicht. Sobald Mehrfachvererbung im Spiel ist, ist diese Objektvergrößerung mitunter erforderlich, weil zwei verschiedene (Sub-)objekte nicht die gleiche Adresse haben können. Für die gewöhnliche einfache Vererbung wird das aber wohl kein Problem sein, wenn dein Compiler nicht von vorgestern ist.



  • So, jetzt habe ich das mit der Vererbung mal ausprobiert und bin jetzt auf einige Compilerfehler gestoßen, aus denen ich absolut nicht schlau werde und die sich ständig verändern, je nachdem, was ich mache.

    template <typename T, int m, int n>
      class basic_matrix
        :public basic_matrix_base<T, m, n>
      {
        public:
        explicit basic_matrix(type);
      };
    

    Das ist im Moment meine kleine Ableitung. type ist in basic_matrix_base definiert:

    //...
        public:
        enum type
        {
          null,
          identity
        };
        //...
    

    Eigentlich sehe ich nicht, was da falsch sein soll, ich kriege aber folgendes:

    matrix.hpp:89: Fehler: nur Deklarationen von Konstruktoren können »explicit« sein
    

    Und wenn ich statt "explicit basic_matrix(type)" "explicit basic_matrix(basic_matrix_base<T, m, n>::type);" schreibe noch seltsamer:

    matrix.hpp:89: Fehler: Typ »rayce::basic_matrix_base<T, m, n>« ist nicht vom Typ »rayce::basic_matrix<T, m, n>« abgeleitet
    

    Ich verstehe kein Wort von diesen Fehlermeldung und habe auch schon alles nach fehlenden ; oder Klammern durchsucht, aber nichts gefunden...



  • Schau mal wie dein "Konstruktor" heißt und wie die Klasse heißt. Du hast da was vergessen.


  • Mod

    basic_matrix_base<T, m, n> ist eine abhängige Basis. Folglich wird type beim Namelookup nicht gefunden werden (das Thema wird hier mindestens einmal pro Woche durchgenommen). also

    template <typename T, int m, int n>
      class basic_matrix
        :public basic_matrix_base<T, m, n>
      {
        public:
        explicit basic_matrix(typename basic_matrix_base<T, m, n>::type);
      };
    

    oder

    template <typename T, int m, int n>
      class basic_matrix
        :public basic_matrix_base<T, m, n>
      {
        public:
        typedef typename basic_matrix_base<T, m, n>::type type; // die enumkonstanten sind damit noch nicht bekannt
        explicit basic_matrix(type);
      };
    

    oder (oder auch nicht, hab das noch nie mit typen und using versucht)

    template <typename T, int m, int n>
      class basic_matrix
        :public basic_matrix_base<T, m, n>
      {
        public:
        using basic_matrix_base<T, m, n>::type;
        explicit basic_matrix(typename type);
      };
    


  • Tut mir leid 😃 Merkzettel: Templates nur benutzen, falls UNBEDINGT notwendig.
    Hab schon typename an alle möglichen Stellen hingeklatscht, da aber noch nicht. Trotzdem doof, dass dadurch die enum-Konstanten nicht bekannt sind, weil mit dem type wollte ich einfaches Konstruieren von Matrizen ermöglichen, die man öfters braucht. Pfff.. in der Zeit hätte ich schon längst ne nicht-Template-Klasse geschrieben und drüber ein typedef für (im Moment noch) double hingeschrieben. Nunja, man lernt nie aus. Vielen Dank, ich werde alle drei Möglichkeiten probieren.


  • Mod

    Eine Möglichkeit ist, Deklarationen, die keine echte Abhängigkeit von Templateparametern haben und auch nicht an eine bestimmte Instanz gebunden sein sollen, in eine eigene Klasse auszulagern und von dieser (ggf. mehrfach und virtuell) zu erben. So wie ich es sehe, taucht type hier überall gleichermaßen auf ohne dass dieses type für verschiedene Templateklassen etwas anderes bedeutet. Es wäre also z.B. auch denkbar, type außerhalb, evtl. in einem eigenen Namespace zu definieren.



  • Ja, genau das habe ich mir auch schon überlegt.



  • Ich bins wieder... Jetzt habe ich zwar alles umgebaut, aber das programmieren an dieser Klasse ist nun eine einzige Qual, weil der Compiler JEDESMAL wenn ich innerhalb einer Methode von basic_matrix (spezialisiert) auf eine Methode von basic_matrix_base zugreifen will ich den vollen Namen der Funktion ausschreiben muss, also basic_matrix_base<T, 2, 2>::foo(). Das wird ziemlich schnell unübersichtlich. Ich bin so genervt, dass ich bald die ganze Klasse schrotten werden und eben zwei Klassen für 3x3- und 4x4-Matrizen schreiben werde. Selbst das wird weniger Redundanz als so.
    Gibt es im angeblich irgendwann kommenden neuen Standard wenigstens eine Möglichkeit, ein Klassentemplate gleichzeitig zu spezialisieren und zu erweitern? Oder hat jemand eine andere clevere Idee, die etwas schneller umzusetzen ist als der neue Standard? 😉
    genervter geloescht

    PS: matrix.hpp:108: Fehler: es gibt keine Argumente für »get_data«, die von einem Templateparameter abhängen, weshalb eine Deklaration von »get_data« verfügbar sein muss


  • Mod

    benutz doch einfach eine using-Deklaration, um den Funktionsnamen bekanntzumachen. Oder schreibt eine kurze inline Funktion, die die geerbte Funktion aufruft. Im neuen Standard könnte man das eleganter durch template typedefs lösen. Dein Problem ist ja, dass die erweiterte Version (für bestimmte Parameter) auf die Grundversion des Templates (mit den selben Parametern) aufbauen soll, und das schließt die unmittelbare Anwendung von Spezialisierung ja aus. Anderseits könnte evtl. die Verwendung eines zusätzlichen Templateparameters mit geeignetem defaultwert helfen. Etwa so:

    template<typename T, int m, int n, bool = (m==3&&n==3) /*hier alle Fälle, die erweitert werden sollen geeignet einschließen)*/>
    class basic_matrix; // wird nicht definiert
    
    // erste Spezialisierung, die Grundform:
    template<typename T, int m, int n>
    class basic_matrix<T, m, n, false>
    // ...
    
    // Spezialisierung für 3x3
    template<typename T>
    class basic_matrix<T, 3, 3, true>
        : public basic_matrix<T, 3, 3, false>
    // ...
    

    Ist systematisch ein bisschen unsauber, spart aber wohl ein wenig code

    Zu überlegen wäre auch noch, ob eine Spezialisierung des Templates überhaupt erforderlich ist. Du kannst ja erstens auch dem Primärtemplate zusätzliche Funktionen spendieren, deren Definitionen für nicht passende Templateargumente nicht instantiierbar sind (oder per SFINAE herausfallen) oder du spezialisiert einzelne Funktionen. Das ist aber auf explizite Spezialisierung beschränkt. Die Beschränkung könnte z.B. dadurch umgangen werden, das diese Funktionen selbst einen Funktor aufrufen, diesen wiederum könntest du auch wieder partiell (also für alle T) spezialisieren.



  • Hmmm, nun, das wird zwar etwas kürzer, aber so richtig zufrieden bin ich damit nicht. Fällt irgendwie unter den Begriff "frickeln" 😉 Ich benutze schon BOOST_STATIC_ASSERT() um Methoden, die nur mit m==n funktionieren zu definieren. Aber die selben Methoden für m==n==3 und m==n==2 müssen ja auch definiert werden und da liegt der Hund begraben. Eigentlich müsste man nur die spezialisieren, aber das geht nicht partiell. Ich kann wirklich nicht glauben, dass da sonst keiner dran gedacht hat. Oder ist meine Matrix-Klasse selbst schrottig? Wie auch immer, ich lasse das mal ruhen und komme wieder darauf zurück, wenn ich Muse habe. Ich finde es aber trotzdem schön, dass du dir Gedanken darüber machst, sonst wäre ich längst zu der Erkenntnis gelangt, dass ich einfach zu blöd dafür bin 😉


  • Mod

    geloescht schrieb:

    Aber die selben Methoden für m==n==3 und m==n==2 müssen ja auch definiert werden und da liegt der Hund begraben.

    Das geht doch ebenso einfach:

    template<typename T, int n>
    class basic_matrix<T, n, n, true>
    // ...
    

    so hast du zwar auch ein template wenn m,n nicht 2 oder 3 ist, aber dieses wird ja dank des bools im Regelfall nicht instantiiert.



  • öhm. Verstehe nicht. Wie kommen dann die zwei Methoden rein? Die sind ja für 2 und drei unterschiedlich.


  • Mod

    unterscheiden sich die Funktionen nur in ihrer Implementation?



  • Ja, und eben in der Klasse, der sie angehören.


  • Mod

    geloescht schrieb:

    Ja, und eben in der Klasse, der sie angehören.

    Dann ist das Problem weitaus einfacher zu lösen, als du anfangs dargestellt hast.
    per SFINAE:

    template<typename T, int m, int n>
    class basic_matrix
    {
    public:
    //    void foo(); wird zu
        typename enable_if_c<m!=m||m!=2&&m!=3,void>::type foo(); // der allgemeine Fall
        typename enable_if_c<m==n&&m==2,void>::type foo(); // m,n == 2
        typename enable_if_c<m==n&&m==3,void>::type foo(); // m,n == 3
    // usw.
    };
    

    Es gibt noch ein paar andere Möglichkeiten, das mittes enable_if zu machen , ich verweise dazu auf die Boost Doku

    Eine andere Möglichkeit:

    template<typename T, int m, int n>
    class basic_matrix
    {
    private:
        template<typename U=T,int o=m,intp=n> friend class DoFoo;
    public:
        void foo() { DoFoo<>()(this); }
    };
    
    template<typename T, int m, int n> class basic_matrix::
    template<typename U, int o, int p> class DoFoo
    {
        void operator()(basic_matrix<T,m,n>*) { /* ... */ }
    };
    
    template<typename T, int m, int n> class basic_matrix::
    template<> class DoFoo<T,2,2>
    // usw.
    

    Indirektion löst noch (fast) jedes Problem.



  • Wahrscheinlich wusste ich nicht, wie ich es anfangs so darstellen konnte, dass man es so lösen könnte 😉
    Das eröffnet neue Horizonte für mich. Auch wenn es wirklich seltsam aussieht. Dankeschön. Wenn ich das ganze wie in der Doku vorgeschlagen als Default-Parameter vom Typ void* mache dann wird der Parameter doch hoffentlich rausoptimiert, wenn ich ihn nicht benutze.



  • darf ich fragen warum du das nicht einfach mit ner if bedingung löst ?



  • Weil Templates zur Compilezeit erstellt werden und ifs zur Laufzeit ausgeführt werden. Außerdem wäre ein if unübersichtlich und etwas langsamer in der Ausführung, was ich nicht will, weil es um ein 3D-Renderingprogramm geht.



  • Ob das jetzt für dich sinnvoll ist oder nicht sei mal dahingstellt, dennoch es ist durchaus möglich relativ leicht if-Statments mit Hilfe von Templates nachzubilden.

    Hier mal ein Beispiel von: http://www.lrz-muenchen.de/~ebner/C++/Slides/cxxkurs.html

    // Klassendeklaration:
    template<bool C>
    class IfThen { };
    
    // Spezialisierung für beide Fälle
    class IfThen<true> {
      public:
        static inline void f()
        { statement1; }
    };
    
    class IfThen<false> {
      public:
        static inline void f()
        { statement2; }
    };
    
    // Ersatz des 'if/else'-Statements:
    IfThen<condition>::f();
    

    Gruß Baracke


Anmelden zum Antworten