Funktionspointer dynamisch generieren



  • Für eine kleine Scriptsprache möchte ich gerne die Möglichkeit bieten, dass Funktionen in der Sprache registriert werden. Dabei wäre die einfachste Möglichkeit natürlich ein Callback dieser Form:

    std::vector<std::string> Callback(std::vector<std::string> Args);
    

    Auf diese Weise kann ich alles ermöglichen. Funktionen mit und ohne Rückgabewert und mit variabler Parameteranzahl.

    Allerdings müsste man dann jede Funktion zumindest wrappen, um sie der Scriptsprache zur Verfügung zu stellen:

    std::vector<std::string> MyAddFunc(std::vector<std::string> Args)
    {
     int x = ToInt(Args[0]);
     int y = ToInt(Args[1]);
    
     return ToString(x + y);
    }
    

    Stattdessen würde ich in meiner Scriptsprache gerne sowas ermöglichen:

    int MyAddFunc(int x, int y)
    {
     return x + y;
    }
    // ...
    scriptEngine->Register("int int int", "Add", MyAddFunc);
    

    Ich hab schon etwas mit std::function experimentiert, aber wie gerneriere ich dynamisch einen Funktionspointer für MyAddFunc anhand von "int int int" und rufe ihn dann mit den Werten aus dem Script auf?

    Ich hab also im \1:

    std::vector<std::string> funcArgs;
    // fill funcArgs
    
    // Wie rufe ich nun MyAddFunc ahnahnd von "int int int" mit funcArgs auf?
    

    Ist das mit variadic Templates möglich? Am besten wäre sogar eine Variante ohne, da ich VS 2010 nutze und es dort noch nicht unterstützt wird.

    Oder geht das evtl. sogar gar nicht?

    Als Typen würde ich ausschließlich int, void und string unterstützen wollen. Alle anderen Funktionen können für die Scriptsprache nicht registriert werden.



  • Was spricht dagegen eine bestehende Skriptsprache zu verwenden? Leider laesst sich dein Problem innerhalb eines Forenbeitrages loesen.



  • knivil schrieb:

    Was spricht dagegen eine bestehende Skriptsprache zu verwenden? Leider laesst sich dein Problem innerhalb eines Forenbeitrages loesen.

    Jetzt weiß ich wieder, was ich unbedingt noch dazuschreiben wollte und leider vergessen habe. Danke. 🙂

    Es handelt sich natürlich um ein Projekt aus reiner Neugier!

    Es geht auch nicht um die Scriptsprache selber (die ist schon fertig). Es geht nur um diese Funktionalität, die ich gerne hinzufügen würde.



  • Also, du kannst Dir aus dem Prototypen der C++-Funktion, die Du binden möchtest, per template-Magie ein Objekt basteln, das die Argumente von Deinem Skriptomaten einliest, in C++-Typen konvertiert und dann den Aufruf startet.



  • Decimad schrieb:

    Also, du kannst Dir aus dem Prototypen der C++-Funktion, die Du binden möchtest, per template-Magie ein Objekt basteln, das die Argumente von Deinem Skriptomaten einliest, in C++-Typen konvertiert und dann den Aufruf startet.

    Danke! 🙂 Da du die Sprache ja echt gut beherrscht, könntest du mir zumindest eine kleine Anregung geben? Das würd mich nochmal um einiges weiter bringen. 👍



  • Das Problem ist, dass hierfür ganz besonders gut Variadic Templates geeignet wären, mit denen ich aber überhaupt keine Erfahrung habe, weil der Hersteller des Compilers meines Unvertrauens es nicht für nötig erachtet hat, diese zeitig einzureichen.
    Daher wendest Du Dich am besten eher an die dynamischen Freunde der Templatemagie hier!



  • Ja, das ist echt schade. Habt ihr denn vielleicht eine Idee, wie ich trotzdem an mein Ziel komme? Vielleicht sogar mit einem anderen Ansatz, der aber auch benutzerfreundlich ist? 🙂



  • Also, ich denke mir das etwa so:

    #include <complex>
    #include <functional>
    #include <iostream>
    #include <sstream>
    #include <stdexcept>
    #include <string>
    #include <tuple>
    #include <type_traits>
    #include <vector>
    
    int foo(unsigned x, double y, std::complex<double> z) {
      std::cout << x << " - " << y << " - " << z << '\n';
      return -1;
    }
    
    template<typename T>
    T from_string(std::string const &s) {
      T result;
      std::istringstream parser(s);
    
      if(parser >> result) {
        return result;
      }
    
      throw std::invalid_argument("Could not convert parameter from string.");
    }
    
    template<typename R, typename... Args>
    class string_function {
    public:
      typedef R (*function_type)(Args...);
    
      string_function(function_type func) : func_(func) { }
    
      std::string operator()(std::vector<std::string> const &str_args) {
        if(str_args.size() != sizeof...(Args)) {
          throw std::invalid_argument("Number of arguments does not match function signature.");
        }
    
        return std::to_string(apply_args(std::integral_constant<bool, sizeof...(Args) == 0>(), str_args));
      }
    
    private:
      template<typename... Args2>
      R apply_args(std::true_type, std::vector<std::string> const &str_args, Args2&&... args2) {
        return func_(args2...);
      }
    
      template<typename... Args2>
      R apply_args(std::false_type, std::vector<std::string> const &str_args, Args2&&... args2) {
        return apply_args(std::integral_constant<bool, sizeof...(Args2) + 1 == sizeof...(Args)>(),
                          str_args,
                          std::forward<Args2>(args2)...,
                          from_string<typename std::tuple_element<sizeof...(Args2), std::tuple<Args...>>::type>(str_args[sizeof...(Args2)]));
      }
    
      function_type func_;
    };
    
    template<typename R, typename... Args>
    std::function<std::string(std::vector<std::string> const &)> make_string_function(R (*func)(Args...)) {
      return string_function<R, Args...> (func);
    }
    
    int main() {
      auto sfunc = make_string_function(foo);
      std::vector<std::string> args { "1", "2.34", "(5, 6)" };
    
      std::cout << sfunc(args) << '\n';
    }
    

    Für Funktionen ohne Rückgabewert müsstest du dir halt was überlegen und eine entsprechende Überladung hinzufügen; gleiches gilt, wenn du statt einfacher Funktionen auch Funktionsobjekte benutzen können willst (Lambdas beispielsweise). Der Ansatz sollte sich entsprechend erweitern lassen, ggf. stellen sich aber Designfragen (ob die Funktionsobjekte kopier- bzw. movebar sein müssen, wem was gehört etc.). Ist halt Arbeit, sollte aber gehen.

    Ohne variadische Templates geht das mit Boost.Preprocessor und viel, viel Gefummel bestimmt auch irgendwie, aber da hab ich wirklich keine Lust drauf.



  • Vielen Dank, seldon. Ich möchte mich bei dir an dieser Stelle nochmal ganz herzlichst bedanken. Insbesondere auch deshalb, weil ich weiß, dass es viele Leute gibt, die sich nach dem Posting eines Vorschlags gar nicht mehr melden.

    Vielen lieben Dank!

    Ich habe den Code noch nicht ausprobieren können (Visual Studio 2010), aber er sieht sehr gut aus und wenn man es etwas auf sich wirken lässt, dann kann man das Konzept dahinter auch verstehen. Darauf kann ich also gut aufbauen.

    Vielen Dank! 👍



  • Funcyd schrieb:

    ...Ich habe den Code noch nicht ausprobieren können (Visual Studio 2010), aber er sieht sehr gut aus...

    Das kannst du vergessen, selbst der aktuelle MSVC (2012) kann noch keine variadischen Templates, die seldon hier benutzt hat.



  • Skym0sh0 schrieb:

    Das kannst du vergessen, selbst der aktuelle MSVC (2012) kann noch keine variadischen Templates, die seldon hier benutzt hat.

    Die November 2012 CTP kanns und spätestens mit dem Update 2 wirds wohl auch production ready sein... 😉



  • Die CTP ist "instabil", bringt keine neue STL mit und ist nicht reif für den produktiven Einsatz.
    @dot, hast Du eine Ahnung, für wann update2 anberaumt ist? Auf den MS-Blogs ist einfach nur Funkstille, ich warte sehnsüchtigst -.-



  • Also man kann das ganze auch umsetzen, indem man das Template spezifisch für alle Anzahlen (nicht typen) von Argumenten, die man unterstützen möchte, überlädt.
    Ist ziemlich viel Cut&Paste-Arbeit, aber ich halte das immernoch für übersichtlicher als da mit dem Präprozessor rumzuspielen, selbst wenn die MS STL das so macht und sich die Boost-Jünger auch darauf eingeschossen haben. Aber als Lib-Entwickler muss man eben etwas flexibles liefern, sehe ich schon ein.



  • Decimad schrieb:

    Also man kann das ganze auch umsetzen, indem man das Template spezifisch für alle Anzahlen (nicht typen) von Argumenten, die man unterstützen möchte, überlädt.
    Ist ziemlich viel Cut&Paste-Arbeit, aber ich halte das immernoch für übersichtlicher als da mit dem Präprozessor rumzuspielen, selbst wenn die MS STL das so macht und sich die Boost-Jünger auch darauf eingeschossen haben. Aber als Lib-Entwickler muss man eben etwas flexibles liefern, sehe ich schon ein.

    Ja, das klingt ganz gut. Ich hab nur ein Problem mit Templates. Wie kann ich eine Klasse realisieren, die eine unterschiedliche Anzahl an Template Argumenten entgegennimmt?

    template <typename R, typename Arg1>
    class FuncPtr
    {
    	typedef R (*func_ptr)(Arg1);
    
    	func_ptr myPtr;
    
    public:
    	FuncPtr(func_ptr ptr) : myPtr(ptr) { }
    
    	std::string operator()(const StrVec& v)
    	{
    		return myPtr(FromString<Arg1>(v[0]));
    	}
    };
    
    template <typename R, typename Arg1>
    std::function<std::string(std::vector<std::string> const &)> FormFuncPtr(R (*func)(Arg1))
    {
    	return FuncPtr<R, Arg1>(func);
    }
    
    template <typename R, typename Arg1, typename Arg2>
    class FuncPtr
    {
    	typedef R (*func_ptr)(Arg1, Arg2);
    
    	func_ptr myPtr;
    
    public:
    	FuncPtr(func_ptr ptr) : myPtr(ptr) { }
    
    	template <typename R, typename Arg1, typename Arg2>
    	std::string operator()(const StrVec& v)
    	{
    		return myPtr(FromString<Arg1>(v[0]), FromString<Arg2>(v[0]));
    	}
    };
    
    template <typename R, typename Arg1, typename Arg2>
    std::function<std::string(std::vector<std::string> const &)> FormFuncPtr(R (*func)(Arg1, Arg2))
    {
    	return FuncPtr<R, Arg1, Arg2>(func);
    }
    

    Folgender Code lässt sich nicht kompilieren, da FuncPtr zu viele Template Argumente hat. Wie könnte man das noch lösen?



  • Wenn ich jeweils eine Variante auskommentiere, dann compiliert es und funktioniert perfekt. Beide zusammen gehen aber nicht, was aber notwendig ist, um unterschiedliche Parameteranzahlen zu unterstützen.



  • Variadic Templates:
    Du nimmst eine mit ihnen und spezialisierst dann für die anderen Varianten.

    Edit: Oh, genau die wolltest du ja vermeiden. Mein Fehler... 😃



  • Anders:

    template< typename T >
    class ScriptCallback;
    
    template< typename Ret >
    class ScriptCallback< Ret() >
    {
       //...
    };
    
    template< typename Ret, typename Arg1 >
    class ScriptCallback< Ret(Arg1) >
    {
       //...
    };
    
    // usw. usf.
    


  • Super, das ist viel besser, danke! Könntest du mir noch sagen, was dieses Ret(Arg1) genau bewirkt bzw. besser macht? 🙂



  • Naja, Ret(Arg1) ist der Typ einer Funktion, die Arg1 nimmt unt Ret zurückgibt. Auf diese Art und Weise bekommt man die ganze Signatur in ein Template-Argument und spezialisiert das Template dann für die "Anzahl der Argumente" des einen Template-Arguments.
    Dein fehlerhaftes Beispiel zeigte ja deutlich auf, dass jede Spezialisierung eines Templates die gleiche Anzahl von Template-Argumenten benötigt.

    So hat man jetzt nicht:

    // achtung fehlerhaft
    template< typename Ret, typename Arg1 > struct templ {};
    template< typename Ret, typename Arg1, typename Arg2 > struct templ {};
    templ< int, float > some;
    templ< int, float, double > some2;
    

    sondern

    template< typename T >
    struct templ; // keine default implementierung
    
    template< typename Ret, typename Arg1 >
    struct templ< Ret(Arg1) > struct templ {}; // << immernoch ein Template-Argument
    
    template< typename Ret, typename Arg1, typename Arg2 >
    struct templ< Ret(Arg1,Arg2) > struct templ {}; // << ein Template-Argumenr!
    
    templ<int(float)> some;   // << 1 -> TEMPLATE <- Argument
    templ<int(float,double)> some2; // << 1 -> TEMPLATE <- Argument
    


  • Alles klar, das macht natürlich so Sinn 🙂

    Jetzt muss ich nur noch schauen, wie ich Funktionen ohne Rückgabewert unterstütze. Da tue ich momentan leider etwas schwer. Na ja, ich probiere mal noch etwas, ansonsten brauche ich wohl ein letztes Mal hilfe. 🙂



  • Funcyd schrieb:

    Jetzt muss ich nur noch schauen, wie ich Funktionen ohne Rückgabewert unterstütze.

    Du meinst void als Rückgabetyp? Das sollte nicht allzu schwierig werden. Es ist sogar möglich, void -Ausdrücke mit return zurückzugeben. Kann bei generischem Code praktisch sein.


Anmelden zum Antworten