Parametertypen eines Funktionszeigers als Template-Parameter?



  • du kannst fuer void spezialisieren:

    template <typename Retval> 
        void foo (Retval (*val) ()) {  }
    

    Problem ist, soweit ich es verstehe, dass void eben kein parameter typ ist. void()(void) ist eben das selbe wie void()() und da gibt es keinen param vom typ void sondern garkeinen parameter. dein template erwartet aber eine funktion mit einem param und du gibst ihr eine mit keinem.

    Edit:
    ich lahme Ente ich 😞



  • 😮 Noch ne Nachteule.



  • Tatsächlich. Dann war das nur ein Denkfehler, und das Minimalbeispiel ist nicht so ganz zutreffend gewesen; mein Problem besteht weiterhin. Ich muß nochmal nachschauen gehen.

    ...ich hab es gefunden.
    Hier tritt es auf:

    struct bar_t
    {
        void bar (int) {}
    };
    
    template <typename Retval, typename Param1>
        void foo (Retval (__closure* val) (Param1)) {}
    
    int main (void)
    {
        bar_t b;
        foo (b.bar);
    }
    

    Allerdings tritt es nur bei der Borland-Compilererweiterung __closure auf. Das höchste, was ich machen kann, ist da wohl, ein Feature-Request an CodeGear zu schicken 😞



  • du hast hier keine funktion void(*)(int) sondern eine funktion void(bar_t::*)(int) , und ja, das ist etwas anderes. (intern implementiert wahrscheinlich als void (*) (bar_t* this, int) )

    die elementfunktion bräuchte nämlich eine instanz, auf die sie wirkt, aber die hat sie nicht. folgendes funktioniert:

    struct bar_t
    {
        void bar (int) {}
    };
    
    template <typename Retval, typename Param1, class C>
        void foo (Retval (C::*val) (Param1)) 
    {
       C x;
       (x.*val) (42);
    }
    
    int main (void)
    {
        foo (&bar_t::bar);
    }
    

    oder du gibst irgendwie ein bar_t objekt mit, für das die elementfunktion aufgerufen werden soll.
    oder du verwendest delegates/funktionsobjekte:

    #include <functional>
    
    struct bar_t
    {
        void bar (int) {}
    };
    
    template <typename T>
        void foo (T fun) 
    {
       fun (42);
    }
    
    int main ()
    {
        bar_t x;
        foo (std::bind1st(std::mem_fun(&bar_t::bar), &x));
    }
    


  • queer_boy schrieb:

    du hast hier keine funktion void(*)(int) sondern eine funktion void(bar_t::*)(int) , und ja, das ist etwas anderes. (intern implementiert wahrscheinlich als void (*) (bar_t* this, int) )

    die elementfunktion bräuchte nämlich eine instanz, auf die sie wirkt, aber die hat sie nicht.

    Doch. Folgendes funktioniert auch:

    struct bar_t
    {
        void bar (int) {}
    };
    
    template <typename Retval, typename Param1>
        void foo (Retval (__closure* val) (Param1)) {}
    
    int main (void)
    {
        bar_t b;
        foo <void, int> (b.bar);
    }
    

    Closures sind nicht gewöhnliche Methodenzeiger, sie liefern ein Objekt gleich mit. Die (nicht standardkonforme) Anweisung b.bar gibt ein solches Closure zurück.
    Die Implementation ist etwa so:

    struct TMethod
    {
        void* Code;
        void* Data;
    };
    

    Hierbei verweist Code auf die Adresse der Methode und Data auf das Objekt.



  • audacia schrieb:

    ...

    __closure
    

    ...

    😮 *wtf*

    tja, mit derlei "Sonderschmökens" kannst Du natürlich alles begründen.
    ...und gleichzeitig belegt das wieder mal, wie sinnvoll es ist, sich auf dem Boden des Standards zu bewegen:

    • Die allermeisten können Dir dabei nicht weiterhelfen,
    • beim nächsten Compiler klappt das auch nicht mehr,
    • queer_boys Aussage bleibt trotzdem richtig, weil er sich auf den Standard bezieht und
    • dieses "Feature" verschleiert einen (dem Standard) wichtigen Unterschied zwischen Member- und freien Funktionen.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    tja, mit derlei "Sonderschmökens" kannst Du natürlich alles begründen.

    ???

    Simon2 schrieb:

    ...und gleichzeitig belegt das wieder mal, wie sinnvoll es ist, sich auf dem Boden des Standards zu bewegen:

    Mag ja sein, aber es gibt Dinge, die in Standard-C++ einfach fehlen. Neben besserer RTTI und Persistenzfeatures ist es z.B. das hier 😉

    Simon2 schrieb:

    Die allermeisten können Dir dabei nicht weiterhelfen

    Im ersten Post ging ich ja auch davon aus, ein compilerunabhängiges Problem zu haben. Im nächsten hatte ich dann festgestellt, daß das Problem nur bei diesem Schlüsselwort auftritt, somit compilerspezifisch und von dessen Hersteller zu beheben ist. Meine Frage hier war damit beantwortet.

    Simon2 schrieb:

    beim nächsten Compiler klappt das auch nicht mehr

    Das nimmt man in Kauf, wenn man sich auf die Lösung eines Herstellers verläßt.

    Simon2 schrieb:

    queer_boys Aussage bleibt trotzdem richtig, weil er sich auf den Standard bezieht

    Er hat argumentiert, wie es wäre, wenn ich einen Methodenzeiger verwendet hätte. Das war zwar richtig, jedoch tat ich das nicht.

    Simon2 schrieb:

    dieses "Feature" verschleiert einen (dem Standard) wichtigen Unterschied zwischen Member- und freien Funktionen.

    Kann es sein, daß du dir dieses "Feature" nochmal ansehen solltest, bevor du so darüber urteilst?
    Alle Alternativen, die in C++ für Delegates aka Closures existieren, sind entweder implementationsspezifische Hacks (Fast Delegates) oder langsam und klobig (boost.function). __closure verschleiert keinen Unterschied, es ist eine compilerspezifische, aber maximal effiziente Implementation von Delegates.

    Und da es z.B. von der VCL intensiv verwendet wird, stellt sich auch gar nicht die Frage, ob man es benutzt oder nicht.



  • Na, dann ist ja alles gut ! 👍

    Gruß,

    Simon2.



  • audacia schrieb:

    Mag ja sein, aber es gibt Dinge, die in Standard-C++ einfach fehlen. Neben besserer RTTI und Persistenzfeatures ist es z.B. das hier 😉

    Stimmt doch garnicht:

    struct bar_t
    {
        void bar (int) {}
    };
    
    template <typename Retval, typename Param1>
    void foo (std::tr1::function< Retval (Param1) > val) {}
    
    int main (void)
    {
        bar_t b;
    	foo <void, int> (std::tr1::bind( &bar_t::bar, &b, _1 ));
    }
    

    😉

    Was klobig angeht, habe ich noch keine Untersuchungen gestartet, ich gehe aber davon aus dass das meiste im Releasemode wegoptimiert wird. Ist die Frage ob der BCC das intern besser handhaben kann. Und von Delegates in z.B. .NET hört man auch nicht immer nur gutes, was das Tempo angeht.



  • Es hat ja niemand etwas gegen Compilererweiterungen, aber warum postest du bei einem Problem mit einer Erweiterung im Standard-C++ Forum wenn es extra ein Forum für den Borland Compiler gibt 💡



  • LordJaxom schrieb:

    Ist die Frage ob der BCC das intern besser handhaben kann.

    Das kann er - weil es ein Sprachmittel ist. Ein Closure enthält (wie weiter oben beschrieben) nur zwei Zeiger: einen auf die Adresse der Methode, einen auf die zugehörige Klasseninstanz. Dementsprechend schnell ist der Aufruf. Das ist auch der wesentliche Vorteil gegenüber std::tr1::function oder boost::function 😉

    Benchmarks findest du z.B. hier:
    http://www.codeproject.com/cpp/FastDelegate2.asp
    (Zur Info: Closures sind noch schneller als alle vier hier - und boost::function ist ganz rechts 😉 )

    Der eigentliche Grund für die Existenz dieser Compilererweiterung war aber die für C++Builder 1.0 angestrebte ABI-Kompatibilität des BCC mit Delphi und die Tatsache, daß Funktionszeiger in Delphi etwas sind, für das es in Standard-C++ kein Äquivalent gibt. Und das war 1997. Ganz abgesehen davon, daß jede standardkonforme Implementation viel zu klobig und C++-spezifisch ist, um in das Delphi-ABI passen zu können: damals gab es so etwas wie std::tr1::function vermutlich noch nicht 😉

    LordJaxom schrieb:

    Und von Delegates in z.B. .NET hört man auch nicht immer nur gutes, was das Tempo angeht.

    Das kann man von .NET eigentlich auch ganz allgemein sagen :p *scnr*

    lolz schrieb:

    Es hat ja niemand etwas gegen Compilererweiterungen, aber warum postest du bei einem Problem mit einer Erweiterung im Standard-C++ Forum wenn es extra ein Forum für den Borland Compiler gibt 💡

    Weil ich zunächst fälschlicherweise annahm, daß es sich nicht um ein compilerspezifisches Problem handelt?
    Das erwähne ich übrigens gerade zum dritten Mal. Du hättest ja einfach mal weiter oben lesen können 🙄



  • Hi,

    ich persönlich finde ja, dass "Performance" nicht das einzige Kritierium zur Beurteilung eines Features sein sollte. C++ macht einfach keinerlei ABI-Aussagen - das kann man mögen (Freiheit für die Compilerentwicklung) oder nicht (ABI-Mix).
    .... aber ist ja alles OK, wenn Du Dein Problem nun gelöst hast.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    ich persönlich finde ja, dass "Performance" nicht das einzige Kritierium zur Beurteilung eines Features sein sollte.

    Natürlich nicht. Der Punkt ist nur: Methodenzeiger in C++ und alle Tricks, die landläufig so benutzt werden, um mit ihnen und einem Objektzeiger Closures zu implementieren, sind viel klobiger, als sie sein müßten. Ein Methodenzeiger, der nicht an ein Objekt gebunden ist, muß darauf vorbereitet sein, auf eine herkömmliche Funktion, eine virtuelle Funktion, eine Funktion aus einer virtuell abgeleiteten Klasse und eine Funktion aus einer von mehreren Basisklassen, für die das Objekt eine andere Adresse hätte, enthalten zu können (die Liste erhebt keinen Anspruch auf Vollständigkeit 😉 ). Das ist zum Teil auch der Ballast, den sich C++ durch die Unterstützung von Mehrfachvererbung aufbürdet. Da ein Methodenzeiger nur die Methode, nicht aber das Objekt kennt, muß er all diese Informationen zur Laufzeit bereithalten und auch darüber entscheiden, wenn er mit einem konkreten Objekt aufgerufen wird.
    Wenn du aber schon bei der Zuweisung einer Methode ein konkretes Objekt angibst - wie das bei Closures der Fall ist -, dann lassen sich die genaue Adresse der Funktion (z.B. VTable) und der passende Objektzeiger (z.B. bei mehreren Basisklassen) schon bei der Zuweisung feststellen. Im Closure müssen also nur ein Methoden- und ein Objektzeiger gespeichert werden, und der Aufruf ist ganze zwei Instruktionen lang:

    push [Data] ; Objektzeiger
    call [Code] ; Funktionszeiger
    

    Simon2 schrieb:

    C++ macht einfach keinerlei ABI-Aussagen - das kann man mögen (Freiheit für die Compilerentwicklung) oder nicht (ABI-Mix).

    Das ist nicht das Problem. Problematisch ist, daß Lösungen, die mit C++-Methodenzeigern arbeiten, nicht binärkompatibel zu Delphis Funktionszeigern gemacht werden können, da sie in jedem Fall mehr als diese zwei Zeiger mit sich herumtragen.

    Simon2 schrieb:

    .... aber ist ja alles OK, wenn Du Dein Problem nun gelöst hast.

    So ist es 😉


Anmelden zum Antworten