Der Typ des Typs eines Typs :)



  • Nein, nein. Das muss doch möglich sein. 🙂
    Ich möchte doch nur die Funktionen aufrufen können. Zur Not bastel ich mir die Typsicherheit halt mit einer simplen Liste, ich kann ja eh nur ein paar Datentypen verwalten.



  • Wie gesagt, nicht ohne Hardcoden.

    template<template<class T> T Function, typename... ParameterTypes>
    class FunctionLogger : public FunctionLoggerBase
    {
    }
    

    So, dann noch ein std::tuple mit den ParameterTypes erstellen, in das du irgendwie die ganzen als String übergebenen Parameter per stringstream reinumwandelst und dann wieder ausgibst.
    Siehe auch hier (leicht ähnliches Problem): http://www.c-plusplus.net/forum/p2023356#2023356

    Und dann alle nötigen Funktionen in eine std::map mit FunctionLoggerBase * reinschmeißen.



  • Sicher kann es möglich sein, dürfte aber mit Bordmitteln von C++ extrem aufwendig sein.
    Wenn du von einer festen Funktionsmenge mit einheitlichen Parametern ausgehst, könntest du eventuell mit einer map mit Funktionszeigern ausgehen. Ansonsten würde ich dafür sorgen, daß ich die Struktur vereinheitlichen kann.



  • @cooky451:
    Im Prinzip läuft es immer darauf hinaus, dass man eine "Dispatch" Funktion hat, der man die "ID" der aufzurufenden Funktion mitgibt, sowie irgend eine art Variant Typ.
    Die Funktion bekommt dann eine Liste oder Map von Variants als Parameter.

    Die Frage ist dann noch, wie der Variant-Typ aussieht, also was der können sollte. Ein paar Möglichkeiten:
    * void* (wenn die aufgerufene Funktion die Typen genau kennt, muss die Info über den Typen ja nicht mit transportiert werden)
    * boost::variant
    * boost::any
    * std::string + Konvertierungs-Funktionen für verschiedene Typen (int, float, etc.)
    * COM VARIANT

    Guck dir vielleicht mal an wie IDispatch von COM implementiert ist. Das macht genau was du möchtest.
    Ebenso könntest du mal gucken wie das C/C++ Interface von Skriptsprachen aussieht, dort findet man auch oft ähnliche Mechanismen.



  • hustbaer schrieb:

    "Dispatch"

    Ich denke, ich habe eine annehmbare Lösung gefunden:

    namespace val
    {
      class Base
      {
      public:
        virtual ~Base() = 0
        {
        }
      };
    
      class String : public Base
      {
      public:
        String(const std::string& val)
          : m_val(val)
        {
        }
        ~String()
        {
        }
        std::string val()
        {
          return m_val;
        }
      private:
        std::string m_val;
      };
    
      class Int : public Base
      {
      public:
        Int(const int& val)
          : m_val(val)
        {
        }
        ~Int()
        {
        }
        long long val()
        {
          return m_val;
        }
      private:
        int m_val;
      };
    
      // ...
    }
    

    dynamic_cast<>() sagt mir dann, was ich da für eine Variable vor mir habe. Was mir noch nicht so gefällt: die Memberfunktion val() (und den Konstruktor?) würde ich ja gerne vererben, allerdings immer mit unterschiedlichem Rückgabewert. Das scheint aber nur mit Templates zu funktionieren, die ich natürlich wieder in keinen Container kriege. Vielleicht hat da ja noch jemand eine Idee?



  • class Base
    {
       ...
    };
    
    template<typename T>
    class MyValueType : public Base
    {
       T value_;
    public:
       MyValueType( const T& value ) : value_( value )
       {
       }
    
       T value() const
       {
          return value_;
       }
    };
    


  • Äh.. das ist natürlich besser, danke, war schon spät gestern. :p



  • Ich traue mich ja kaum zu fragen, aber hat jemand eine Idee wie man hier noch das "new" umgehen kann?
    Konkret habe ich hier einen std::vector<VarBase*>, auf den ich meine Variablen dann mit v.push_back(new Var<typ>(wert)) packe. Ist nur recht nervig, das wieder freizugeben.



  • cooky451 schrieb:

    Ich traue mich ja kaum zu fragen, aber hat jemand eine Idee wie man hier noch das "new" umgehen kann?
    Konkret habe ich hier einen std::vector<VarBase*>, auf den ich meine Variablen dann mit v.push_back(new Var<typ>(wert)) packe. Ist nur recht nervig, das wieder freizugeben.

    Um was geht es dir? Das new zu umgehen? Einen eigenen allocator schreiben der in Junks allokiert. Oder um den Komfort? Dann boost::ptr_vector nehmen.

    PS:
    damit meine ich keinen allocator fuer den vector, sondern einen allocator der dir das new ersetzt.



  • vector<shared_ptr<VarBase*>> wird's werden, falls der vector überhaupt nötig ist. Ausnahmsweise mal.
    Weil z.B. der Optimierer Ausdrücke umwursteln können will, ohne sich um die in einem fernen vector befindlichen besitzverhältnisse zu kümmern. Oder Objekte so lange leben lassen, wie ein Name dafür existiert, respektive in irgend einer Bezeichnertabelle eingetragen ist.



  • Boost möchte ich nicht mit reinziehen.

    Shade Of Mine schrieb:

    eigenen allocator schreiben

    Habe ich noch nie gemacht. Hast du zufällig einen guten Link oder so etwas? Sonst muss Google wieder herhalten. 🙂

    volkard schrieb:

    falls der vector überhaupt nötig ist.

    push wäre schon gut, std::stack würde auch gehen, aber ist das besser?

    Edit:
    Abgesehn davon scheint mir auto_ptr<> ganz ok zu sein, das new bekomme ich zwar nicht weg, aber immerhin das delete. Oder gibt es da ein gutes Gegenargument?



  • int Dispatch(int functionId, VarBase const* params, size_t paramCount);
    
    template <size_t N>
    int Dispatch(int functionId, VarBase const (&params)[N])
    {
        return Dispatch(functionId, &params[0], N);
    }
    
    void foo()
    {
        Var<int> a1 = ...;
        Var<std::string> a2 = ...;
        Var<Foo> a3 = ...;
    
        VarBase const* params[] = {&a1, &a2, &a3};
        Dispatch(MyFunctionId, params);
    }
    

    Kein new, kein delete, kein manuelles Aufräumen, kein std::vector, trotzdem keine Leaks. Alles gut 🙂

    Und man kann sich natürlich auch ein paar Overloads schreiben, vielleicht für bis zu 10 Parameter oder so...

    int Dispatch(int functionId)
    {
        return Dispatch(functionId, static_cast<VarBase const*>(0), static_cast<size_t>(0));
    }
    
    int Dispatch(int functionId, VarBase const& a1)
    {
        VarBase const* params[] = {&a1};
        return Dispatch(MyFunctionId, params);
    }
    
    int Dispatch(int functionId, VarBase const& a1, VarBase const& a2)
    {
        VarBase const* params[] = {&a1, &a2};
        return Dispatch(MyFunctionId, params);
    }
    
    int Dispatch(int functionId, VarBase const& a1, VarBase const& a2, VarBase const& a3)
    {
        VarBase const* params[] = {&a1, &a2, &a3};
        return Dispatch(MyFunctionId, params);
    }
    
    // ...
    

    Oder gleich Template-Overloads die auch das Einwickeln der Parameter mit übernehmen:

    template <class A1>
    int Dispatch(A1 const& a1)
    {
        Var<A1> va1 = a1;
        VarBase const* params[] = {&va1};
        return Dispatch(MyFunctionId, params);
    }
    
    template <class A1, class A2>
    int Dispatch(A1 const& a1, A2 const& a2)
    {
        Var<A1> va1 = a1;
        Var<A2> va2 = a2;
        VarBase const* params[] = {&va1, va2};
        return Dispatch(MyFunctionId, params);
    }
    
    // ...
    

    Und mit Variadic-Templates geht's vermutlich noch viel schöner (müsste aber erst die Syntax nachgucken, da ich die noch nie verwendet habe).

    EDIT: mir fällt gerade auf, wenn alle "Dispatch" Funktionen gleich heissen, ist das vermutlich nicht ideal. Vermutlich sollte man alles bis auf die Template-Overloads "DispatchRaw" oder so nennen, damit immer eindeutig ist welche Funktion jetzt wirklich aufgerufen werden soll. Aber egal, das Prinzip sollte klar sein.



  • cooky451 schrieb:

    Edit:
    Abgesehn davon scheint mir auto_ptr<> ganz ok zu sein, das new bekomme ich zwar nicht weg, aber immerhin das delete. Oder gibt es da ein gutes Gegenargument?

    auto_ptr<> ist in C++0x deprecated - oder ist er schon jetzt deprecated und ihn gibt es in C++0x gar nicht mehr? Jedenfalls wird dir bestimmt gleich entweder Google oder ein Benutzer erzählen, warum.



  • @hustbaer
    Das ist nett, aber es wird erst zur Laufzeit entschieden wie viele Parameter einer Funktion übergeben werden. Das dürfte dann mit deiner Variante schwierig werden, oder übersehe ich da etwas? Deswegen meinte ich, dass ich einen Container brauche der zumindest push() anbietet.

    wxSkip schrieb:

    auto_ptr<> ist in C++0x deprecated - oder ist er schon jetzt deprecated und ihn gibt es in C++0x gar nicht mehr? Jedenfalls wird dir bestimmt gleich entweder Google oder ein Benutzer erzählen, warum.

    Tja, das ist doof. Die Idee mit dem "new" gefiel mir allerdings eh nicht besonders, ich sehe nur gerade keine Alternative.



  • Also wenn niemand eine mich bahnbrechend glücklich machende Alternative hat, werde ich wohl

    std::vector<std::unique_ptr<VarBase>>
    

    nutzen. 🙂



  • Hm. Hmmmmmmmm.

    Wenn das ein "stabiles" Interface werden soll, also die ur-unterste Dispatch Funktion möglichst lange ohne Änderungen auskommen soll, dann würde ich vermutlich trotzdem einfach ein rohes Array aus rohen Zeigern nehmen (VarBase const* + size_t).

    Weil es dem Aufrufer nix aufzwingt, was evtl. mal lästig werden könnte.

    Darüber kann man natürlich gerne eine Funktion legen, die einen vector<unique_ptr<VarBase>> nimmt, oder auch einen ptr_vector<VarBase>.

    Wenn der Code "lebendig" bleibt, also auch mal einfach geändert werden darf/kann, ist das weniger wichtig. Dann kann man es ja jederzeit refactoren wenn die vector<unique_ptr<VarBase>> Version mal stören sollte.

    Obwohl ich persönlich vermutlich trotzdem das "rohes Array aus rohen Zeigern" Interface wählen würde. Mir erscheint das einfach richtiger. Ich muss dem Aufrufer ja nicht aufzwingen seine Parameter in unique_ptr zu verwalten, wenn es nicht nötig ist. Vielleicht mag er seine Parameter ja an bestimmten Stellen auf den Stack legen, oder in shared_ptr tun, oder was auch immer.



  • hustbaer schrieb:

    Vielleicht mag er seine Parameter ja an bestimmten Stellen auf den Stack legen

    Das verstehe ich nicht. Wie kann er seine Parameter auf den Stack legen?

    Wie auch immer, das hat mich irgendwie auf eine Idee gebracht. Dass die Parameter nicht auf dem Stack liegen nervt mich eh, so etwas verlangsamt/verkompliziert alles ja nur, zumal ja keine Parameter in riesigen Größenordnungen verarbeitet werden müssen. Momentan sieht das Ganze so aus:

    functions[name]->run(read_params())
    // wobei read_params einen std::vector<std::unique_ptr<VarBase>> zurückgibt.
    

    Eigentlich könte man das auch ändern in

    const std::size_t size = 16; // oder was auch immer
    VarBase params[size];
    std::size_t valid = read_params(params, size);
    functions[name]->run(params, valid);
    

    So hat man gleich alles auf dem Stack. Nachteil ist natürlich, dass ich VarBase nicht mehr pure virtual halten kann. Ist das in etwa das, was du mir raten wolltest?



  • Nö, nicht wirklich 🙂

    Was ich mit Stack meine, hab ich ja schon mit Beispielcode gepostet. Du sagst das brauchst/willst du nicht, aber vielleicht will man ja doch mal irgenwo eine Funktion so aufrufen können.

    Es gibt aber so unzählig viele Möglichkeiten wie man sowas machen kann, und wie man sowas optimieren kann.

    Und... je nachdem wie dein restlicher Code aussieht, kann leicht sein, dass jegliche Optimierung die man da machen könnte, vollkommen sinnlos ist, weil sowieso der restliche Code viel viel langsamer ist.



  • Hier was einfaches:

    #include <iostream>
    #include <map>
    #include <sstream>
    
    //Begin Framework
    std::map<std::string,void(*)(std::istream& in)> theMap;
    
    bool registerFunction(std::string name,void(*pf)(std::istream& in)){
        theMap[name]=pf;
        return true;
    }
    
    void call(std::istream& in){
        std::string name;
        in>>name;
        (*theMap[name])(in);
    }
    
    void call(std::string const& str){
        std::istringstream in(str.c_str());
        call(in);
    }
    //End Framework
    
    //Normale C++-Funktion
    void test(std::string name,int alter,int masse){
        std::cout<<name<<" ist "<<alter<<" Jahre alt und wiegt "<<masse<<" kg.\n";
    };
    //Wrapper für Gamekonsole-Aufruf
    void testWrapper(std::istream& in){
        std::string name;in>>name;
        int alter;in>>alter;
        int masse;in>>masse;
        test(name,alter,masse);
    }
    //Wrapper registrieren
    bool testInit=registerFunction("test",&testWrapper);
    
    //Test
    int main(){
        call("test Hans 25 82");
        call("test Hubert 26 83");
    }
    

Anmelden zum Antworten