dynamic template parameters



  • Moin,

    ich habe mehr oder weniger ein logistisches Problem, als ein Codetechnisches Problem. Ich habe eine Klasse Delegate, die wiederum auf eine templatestruct mit mehreren Spezialisierungen zugreift. In dieser Struct befindet sich ein Pointer mit nicht näher definiertem Typ T. So ist es möglich mehrere Arten von Methodenpointern (stdcall, cdecl) mit der selben struct zu verwenden. Aufgerufen werden die Pointer dann per Invoke, was wiederum für bis zu 8 parameter getemplated ist.

    template<typename T>
    struct delegate
    {
      T ptr;
    
      template<typename returnType, typename arg0 ... n> inline returnType Invoke(arg0 a0 ... aN) {return ptr(a0... aN);}
    }
    

    Wenn ich jetzt ein Objekt vom Typ delegate in der Wrapperklasse anlege, muss ich ja jedes mal die Pointerdefinition angeben. Daher hatte ich mir gedacht per Konstruktor die Definition individuell erzeugen zu lassen, je nach ob eine Instanzmethode oder eine static verwendet werden soll etc. (ich muss natürlich weiterhin die Parameterliste angeben) und dachte mit ich verwende eine Basisklasse dafür und erzeuge die Ableitung der jeweiligen Definition je nach Konstruktoraurfruf.

    //spezialisierung für return type und keinen parameter als veranschaulichte Darstellung
    template<typename returnType>
    class Delegate<...>
    {
      void* owner;
      delegate_base ptr;
    
      public:
         Delegate(void* func) : ptr(delegate<returnType (*ptr) (), returnType>(func)) {}
         template<class own> Delegate(void* func, own* owner) : ptr(delegate<returnType (own::*func) (), returnType>(func)), owner(owner) {}
    
        inline returnType Invoke() {return ptr.Invoke<>();}
    }
    

    Das gestaltet sich jedoch schwieriger als gedacht, da die getemplateten Methoden die der basisklasse weder mit virtual überschrieiben können, noch durch eine einfache abstract klasse genutzt werden können.
    Ich habe auch schon versucht mit einem void-Pointer und einem Typecast im gewrappten Invoke zu arbeiten, das hat aber das Problem lediglich bis zum Cast verlagert.

    Kann ich mir den type nicht einfach irgendwo speichern oder hat Irgendjemand eine andere Idee wie sich das am einfachsten lösen lässt ohne die Methodendefinition an den Anwender weiterleiten zu müssen??

    ~Ich möchte damit übrigens nicht nur auf die so heiß geliebten OpenGL-Pointer nebst header verzichten, sondern auch für etliche andere Situationen dynamische Erweiterungen ermöglichen.~



  • Dein Vorhaben ist so nicht möglich. Auch bist du einem Verständnisproblem aufgesessen.

    Es handelt sich bei Delegate nicht um ein Template-Struct, sondern um ein Struct-Template bzw. eine Struct-Vorlage, aus der Structs gestanzt werden können. Genauso ist es bei dem, was du "getemplatete Methoden" nennst -- das sind keine Methoden, sondern Methodenvorlagen, aus denen nach Bedarf vom Compiler Methoden gestanzt werden. Deshalb können diese auch nicht virtual sein -- virtual bedeutet, dass ein Zeiger auf eine Methode in der virtuellen Funktionstabelle steht, und auf eine Vorlage, die nur der Compiler kennt und die zur Laufzeit nicht mehr existiert, kann man keinen Zeiger nehmen.

    Was du auf jeden Fall machen musst, ist, das Interface dem Anwender bekannt zu machen, damit der Compiler an dieser Stelle den richtigen Code erzeugen kann. Es ist ja nicht möglich, eine Funktion zu haben, die einen Funktionszeiger, der drei Argumente annimmt, mit fünfen aufruft. Dafür gibt es im neuen Standard auch eine Klassenvorlage namens std::function:

    #include <functional>
    #include <iostream>
    
    struct hello {
      void say() const { std::cout << "Hello, world.\n"; }
    };
    
    void sag_hallo(hello const &) {
      std::cout << "Hallo, Welt.\n";
    }
    
    void invoke(std::function<void(hello const &)> const &func) {
      hello h;
      func(h);
    }
    
    struct twice_type {
      double operator()(double x) const { return x * 2; }
    };
    
    int main() {
      std::function<void(hello const &)> f1(sag_hallo);
      std::function<void(hello const &)> f2(&hello::say);
    
      invoke(f1);
      invoke(f2);
      invoke(&hello::say); // auch ad hoc (Konstruktor halt)
    
      // Überladener Klammer-Operator reicht da auch.
      std::function<double(double)> twice((twice_type()));
    
      std::cout << twice(2.0) << '\n';
    }
    

    Wenn du auf einem alten Compiler festsitzt und C++11 noch nicht verwenden kannst, nimm stattdessen Boost.Function, das funktioniert im Wesentlichen genauso (allerdings mangels variadischer Templates mit begrenzter Parameterzahl).

    Das ist allerdings auch das höchste der Gefühle. Ein dynamisches Typsystem hat C++ nicht.



  • ja ne, das das vorlangen sind ist mir klar, es ist mir allerdings nicht klar, warum ich dem compiler nicht zu einem bestimmten Zeitpunkt sagen kann "bitte änder die definition von delegate<void (*ptr) ()> nach delegate<void (class*ptr) ()>" weil er doch mit aufruf des konstruktors theoretisch genau weiß welche vorlagenparameter ich verwenden möchte oder seh ich das falsch?

    Zumindest beschwert er sich bei Fehlern erst wenn ich die entsprechende Methode in einer cpp verwenden möchte, von daher weiß er ja auch welche Methode ich verwende und könnte ja theoretisch sowas herauslesen oder nicht?


Log in to reply