Pointer auf freistehende Funktions-Templates zur Laufzeit füllen



  • Hi. Ich habe in meinem Code freistehende Funktionstemplates. Nun möchte ich einen Funktionspointer hinzufügen, der erst zur Laufzeit über eine init Funktion mit einer von beiden (zukünftig auch erweiterbar) Funktionen gefüllt wird. Ist das bei templates überhaupt möglich?

    template <typename T> void function_1 (T *data) { /* do something */ }
    template <typename T> void function_2 (T *data) { /* do something */ }
    template <typename T> void (*function_ptr)(T *data) = nullptr;
    
    // Geht nicht da dies kein Funktionstemplate ist und somit kein Templateparameter weitergegeben werden kann.
    // Mache ich ein Funktionstemplate daraus, muss ich beim Init schon wissen welche Datentypen genutzt werden und das macht den Sinn dieser Funktionen kaputt.
    void init ()
    {
        // könnte eine Nutzereingabe sein oder auch die Abfrage welche OS Version vorliegt oder irgendetwas, dass sich nur zur Laufzeit feststellen lässt
        if (some_condition)
            function_ptr = function_1;
        else
            function_ptr = function_2;
    }
    

    Gibt es denn einen schönen Lösungsweg dafür? Ich würde ungern eine freistehende Wrapper-Funktion o.ä. nutzen, die jedes mal beim Aufruf die Bedingung prüft uns den Aufruf mappt. Auch möchte ich nicht aus der Init Funktion ein template machen und es pauschal mit allen Datentypen instanziieren, nur damit die Pointer belegt sind.

    besten Dank für die Hilfe, Jason



  • Ein Funktionstemplate ist eine Vorlauge (template) für eine "Familie" von Funktionen.
    Daraus entstehen dann, wenn man es instanziert, verschiedene Funktionen.

    Das andere Ding das du da hast, das du function_ptr nennst, ist eine Vorlage (template) für eine "Familie" von Zeigervariablen. Daraus entstehen dann, wenn man es instanziert, verschiedene Zeigervariablen.

    Die Vorlange als solche hat nur vor/während dem Kompilieren des Programms relevanz, danach existiert die nicht mehr -- danach gibt es nur die nach dem jeweiligen Rezept der Vorlage gebackenen Dinge (Klassen, Funktionen, Variablen).

    Damit sollte klar sein, dass du die Vorlage nicht wie eine Variable zuweisen kannst -- es gibt einfach keine Variable namens function_ptr. Es gibt vielleicht eine namens function_ptr<int> und eine namens function_ptr<char> bzw. mit was auch immer du eben das Template instanziert hast.

    Da ich nicht genau weiss was du damit anstellen willst, kann ich aber nur raten was eine gute Lösung wäre. Möglicherweise das Visitor-Pattern. Vielleicht auch einfach nur ein Abstract-Interface.

    Auf jeden Fall wirst du dich irgendwie auf eine konkrete Liste von Typen einschränken müssen oder auf eine konkrete Liste von Funktionen.

    Ich würde ungern eine freistehende Wrapper-Funktion o.ä. nutzen, die jedes mal beim Aufruf die Bedingung prüft uns den Aufruf mappt.

    Naja ich fände das nicht schlimm. Dafür kann man ja wieder ein Funktionstemplate machen. Ala

    template <typename T> void function_1(T *data) { /* do something */ }
    template <typename T> void function_2(T *data) { /* do something */ }
    int function_id = 0;
    
    template <typename T> void function_dispatcher(T *data)
    {
        switch (function_id) // Quasi gratis so lange der Code im I-Cache und der Sprung im Branch-Target-Buffer bleibt
        {
        case 1:
            function_1(data);
            break;
        case 2:
            function_2(data);
            break;
        default:
            assert(0 && "internal error");
        }
    }
    
    void init()
    {
        if (some_condition)
            function_id = 1;
        else
            function_id = 2;
    }
    


  • Hi hustbaer, danke für die prompte und ausführliche Antwort!

    Ich bin im Videobereich tätig und schreibe dort performance-kritische Realtime-Anwendungen. Alle Aufgaben die nicht gerade über Shader ausgeführt werden oder rein im Software-Renderer vorkommen, haben wir sehr gut optimieren können, indem sowohl GPU als auch CPU Eigenschaften oder Instructionssets berücksichtigt werden. Ähnlich wie bei OpenGL, wo die Funktionspointer erst zur Laufzeit und abhängig vom GraKa-Treiber geladen werden können, würden wir eben dann zur Laufzeit noch interne Funktionen auf die jeweils performanteste Version für dieses System mappen.

    Bei Klassen funktioniert das soweit ganz gut, da wir dort entweder im Konstruktor die Bedingungen auswerten und Memberfunktionspointer entsprechend zuweisen oder mittels Vererbung & Factory passende Instanzen erzeugen können. Auch spielt da das Template System keine große Rolle, da alles innerhalb des Objekts stattfindet. Problematisch sind eben viele einfache, freistehende Funktionen, die auch oft an diversen Stellen aufgerufen werden.

    Es gäbe die Möglichkeit alles umständlich zu regeln, indem wir z.B. beim Verbraucher verschiedene DLLs abhängig vom System installieren und laden. Aber das treibt bei allen Kombinationsmöglichkeiten die Kompilierzeit und den Pflegeaufwand des Codes unnötig nach oben und auch da müssten wir die templates explizit instanziieren.

    Es ist gerade noch nicht so ganz abzusehen wie sehr so ein (oder verschiedene) Dispatcher im Umfang explodieren würden, das werde ich mir morgen nochmal anschauen müssen.



  • @Jason sagte in Pointer auf freistehende Funktions-Templates zur Laufzeit füllen:

    bla bla

    Es ist scheißegal. Ein template muss instantiiert werden damit du es benutzen kannst.



  • @Swordfish Danke für deine sehr hilfreiche Antwort. Das habe ich bereits nach hustbaers Antwort verstanden 😉



  • @hustbaer sagte in Pointer auf freistehende Funktions-Templates zur Laufzeit füllen:

    …
        switch (function_id) // Quasi gratis so lange der Code im I-Cache und der Sprung im Branch-Target-Buffer bleibt
        {
        case 1:
            function_1(data);
            break;
        case 2:
            function_2(data);
            break;
        default:
            assert(0 && "internal error");
        }
    }
    …
    

    Genau für solche Fälle wurde einmal Polymorphismus eingeführt.



  • Dieser Beitrag wurde gelöscht!


  • @Jason sagte in Pointer auf freistehende Funktions-Templates zur Laufzeit füllen:

    Problematisch sind eben viele einfache, freistehende Funktionen, die auch oft an diversen Stellen aufgerufen werden.

    Schwer zu sagen, was ihr genau habts oder braucht, aber hast du schon mal an dynamische Codegenerierung gedacht? Mit LLVM oder evtl. auch einfach asmjit ist das nicht so wahnsinnig kompliziert.