Der Typ des Typs eines Typs :)



  • Dann ist halt der Wert eine Struktur mit einer id für den Typen und einem Wert, den du casten kannst.

    Ich bezweifle allerdings immer noch, dass du das so wirklich brauchst. Was soll denn die Funktion machen? Willst du ein printf nachbauen oder wie?



  • Du willst Reflection. Du willst die Erstellung dynamischer Variablen. Das geht in C++ nicht ohne Hardcoden oder einer Menge Makros oder beidem.

    D.h. du nimmst z.B. eine std::unordered_map<std::string, std::vector<std::unique_ptr<AbstractFunctionBase> > > oder so etwas und speicherst dann alle Funktionen darin. Den Rest mit den Parametern musst du dir ebenfalls irgendwie zusammentricksen. Viel Spaß.

    P.S.: Hat die Funktion "funktion" in deinem Beispiel garantiert die Parameter int, std::string? Kann sie überladen sein?



  • volkard schrieb:

    Ist die Typsicherheit von C++ gewünscht, also daß Du direkt einen Fehler bekommst, wenn es keine passende Funktion gibt?

    Das wäre gar nicht schlecht, allerdings denke ich nicht, dass das so wie ich mir das gerade vorstelle funktioniert.
    Kleine Scriptsprache trifft es eigentlich ganz gut. Ich möchte einer Klasse die Funktionen x, y, z, ..., bekannt machen und dann von außen auf diese zugreifen können.

    Edit:
    Templates und Überladungen sind nicht nötig. Nur simple Funktionen. 🙂



  • cooky451 schrieb:

    volkard schrieb:

    Ist die Typsicherheit von C++ gewünscht, also daß Du direkt einen Fehler bekommst, wenn es keine passende Funktion gibt?

    Das wäre gar nicht schlecht, allerdings denke ich nicht, dass das so wie ich mir das gerade vorstelle funktioniert.
    Kleine Scriptsprache trifft es eigentlich ganz gut. Ich möchte einer Klasse die Funktionen x, y, z, ..., bekannt machen und dann von außen auf diese zugreifen können.

    Wenns nicht so viel ist, einfach sehr simpel hardcoden (noch direkter als in meinem Beispiel). Wenn es mehr ist, würde sich evtl. ein selbst geschriebener Parser/Interpreter oder eine andere Programmiersprache eignen.



  • Würden Lua oder boost::python oder AngelScript *ALLE* Deine Träume erfüllen, aber zu Kosten von ein wenig Einarbeitungsaufwand?
    Oder sind die schief zu Deinem Problem?



  • wxSkip schrieb:

    Wenns nicht so viel ist, einfach sehr simpel hardcoden (noch direkter als in meinem Beispiel).

    Ne, das ist eigentlich nicht das, was ich erreichen möchte.

    volkard schrieb:

    Oder sind die schief zu Deinem Problem?

    Ziemlich, denn genau das hier ist mein Problem, da steckt (noch?) kein tieferer Sinn dahinter. Ich wollte nur mal ausprobieren, wie man so etwas schreiben könnte und jetzt habe ich mich hoffnungslos darin verbissen. 🙂



  • cooky451 schrieb:

    kein tieferer Sinn dahinter. Ich wollte nur mal ausprobieren, wie man so etwas schreiben könnte und jetzt habe ich mich hoffnungslos darin verbissen. 🙂

    Wenn Du selber machen wollst, fanch mit einem mathematischen Parser an.
    Am besten recursive descent.
    Der erstellt einen Baum, wo die Knoten Ausdrücke sind.
    Den kannste dann erweitern, daß die Knoten Kommandos, Definitionen, Deklarationen sind.
    Das macht viel Spaß.



  • cooky451 schrieb:

    volkard schrieb:

    Oder sind die schief zu Deinem Problem?

    Ziemlich, denn genau das hier ist mein Problem, da steckt (noch?) kein tieferer Sinn dahinter. Ich wollte nur mal ausprobieren, wie man so etwas schreiben könnte und jetzt habe ich mich hoffnungslos darin verbissen. 🙂

    Mein Tipp: Lass es. Mir kommen auch manchmal solche Ideen. Eine Any-Klasse ohne typeid() war gerade noch umzusetzen, aber sobald man mit Träumereien von beliebigen Programmen, die ihren Status jederzeit abspeichern und sich beim nächsten Programmstart genau an der selben Stelle mit den gleichen Daten wieder fortsetzen können, anfängt, sollte man besser aufhören. Es sei denn, man wird für diese "Visionen" bezahlt. 😃



  • 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.


Anmelden zum Antworten