Zeiger auf Funktion



  • Hallo,

    mir ist folgender Aufruf einer Funktion über einen Funktionszeiger nicht ganz klar (Zeile (*)):

    ostream& operator<<(ostream& s, omanip<T>& fobj)
    {
    	return (*fobj.funktPtr)(s, fobj.arg); // (*)
    }
    

    Vielleicht auch der Vollständigkeit halber noch die Klasse omanip:

    template<class T>
    class omanip
    {
    	ostream& (*funktPtr)(ostream&, T);
    	T arg;
    public:
    	omanip(ostream& (*f)(ostream&, T), T obj)
    	: funktPtr(f), arg(obj)
    	{}
    
    	friend ostream& operator<<(ostream&, omanip<T>&);
    };
    

    Und zwar geht es mir um die Dereferenzierung in Zeile (*), falls es überhaupt eine ist. Liegt es daran, dass der Wert von fobj.funktPtr (also *fobj.funktPtr) die Funktion selbst ist?
    Und kann ich nicht trotzdem einfach schreiben:

    return fobj.funktPtr(s, fobj.arg);
    

  • Mod

    Nanuq schrieb:

    Hallo,

    mir ist folgender Aufruf einer Funktion über einen Funktionszeiger nicht ganz klar (Zeile (*)):

    Und zwar geht es mir um die Dereferenzierung in Zeile (*), falls es überhaupt eine ist.

    Ja, ist es.

    Liegt es daran, dass der Wert von fobj.funktPtr (also *fobj.funktPtr) die Funktion selbst ist?

    Im Prinzip, ja.

    Und kann ich nicht trotzdem einfach schreiben:

    return fobj.funktPtr(s, fobj.arg);
    

    Ja, darfst du.



  • 👍 Danke!

    camper schrieb:

    Im Prinzip, ja.

    Was sich hinter "Im Prinzip" verbirgt, frage ich besser gar nicht, bevor ich es nachher wieder nicht mehr verstehe 😉


  • Mod

    wenn du auch mal 5-Sterne-Progger sein willst:

    void foo() {}
    void bar()
    {
        foo();
        (foo)();
        (*foo)();
        (**foo)();
        (***foo)();
        (****foo)();
        (*****foo)();
    // usw.
    }
    


  • 😕 ich glaube, zum 5-Sterne-Progger fehlt noch ein Stück, ich steige schon vor dem ersten aus 🤡

    Lass mich's trotzdem mal versuchen:

    void foo() {}
    void bar()
    {
        foo();
        // so hätte ich es verstanden
        void (*fooptr)() = foo;
        fooptr();
    
        // so geht es auch, stelle ich gerade fest, obwohl ich es nicht wirklich verstehe
        void (*fooptr)()(foo);
        fooptr();
    
        // zum nächsten Schritt fehlt trotzdem noch ein Stück...
        (foo)();
    
        // wenn (foo)(); funktioniert, ist irgendwie klar, dass der Rest auch funktionieren wird.
        (*foo)();
        (**foo)();
        (***foo)();
        (****foo)();
        (*****foo)();
    // usw.
    }
    

    Da findet doch jetzt alles irgendwie auf einmal statt:
    - Deklaration eines (temporären) Zeigers auf eine Funktion
    - Initialisierung mit foo
    - (Dereferenzierung, die ja, wie ich eben gelernt habe, nicht unbedingt erforderlich ist)
    - Funktionsaufruf über den Zeiger

    Lässt sich das so erklären, dass selbst ich es verstehe 🙂


  • Mod

    void (*fooptr)() = foo;
    

    Definition eines Funktionspointers fooptr mit Initialisierung. Der initialisierende Ausdruck ist eine Lvalue vom Typ Funktion mit geeigneten Parametern und Rückgabewert. Skalare Typen (also auch Pointer) werden allerdings durch rvalues vom gleichen Typ initialisiert. Also kommt hier die Standardkonvertierung von Funktion zu (rvalue) Pointer auf Funktion zur Anwendung.

    fooptr();
    

    Der Ausdruck fooptr ist ein lvalue vom Typ Pointer auf Funktion(...) Ein Funktionsaufruf kann entweder mit einem lvalue eines Funktionstyps oder einem rvalue eines Pointers auf Funktion durchgeführt werden. Also kommt es hier zu einer lvalue-zu-rvalue Konvertierung des Pointers (was für gewöhnlich dem Lesen des Pointers gleichkommt) und dann der Aufruf der Funktion mittels des Ergbnisses dieser Konvertierung.

    void (*fooptr)()(foo);
    

    Eine andere syntaktische Form der Initialisierung. Für skalare Typen gibt es keinen Unterschied zur Initialisierung mittels =. Man kann sich vorstellen, dass diese Typen eine Art Pseudokonstruktor haben, muss man aber nicht.

    (foo)();
    

    Die Klammern bewirken gar nichts. foo ist ein lvalue vom Typ einer Funktion(...), und (foo) ändert daran nichts. Es ist also ein gewöhnlicher Funktionsaufruf.

    (*foo)();
    

    Dieser Fall ist interessant. foo ist wieder ein lvalue einer Funktion(...). Der eingebauten unäre Operator * (selbstdefinierte Operatoren kommen nicht in Frage, denn Funktionen sind keine UDTs) kann auf rvalues eines Objektpointers und auf rvalues von Pointers auf Funktionen angewendet werden. Eine implizite Konvertierung eines lvalues einer Funktion in ein rvalue eines Objektpointers ist nicht möglich, wohl aber die in ein rvalue eines Pointers auf Funktion. Das Ergebnis des Operators * ist ein lvalue, das - Überraschung! - auf die Funktion foo verweist. Das kann man beliebig wiederholen (und auch ein paar & Operatoren einstreuen, wenn man Spaß daran hat). Schließlich wird der Funktionsaufrufsoperator() auf diese lvalue angewendet, was zum Funktiosaufruf führt.



  • Danke für die ausführliche Antwort!
    Ich glaube, ich habe es einigermaßen begriffen.

    Trotzdem nochmal eine Rückfrage (ich hoffe, ich nerve nicht allzu sehr ;)): Erfolgt diese Konvertierung von lvalues in rvalues immer implizit, oder gibt es Fälle, in denen man sie explizit vornehmen muss bzw. sollte (falls das überhaupt geht)?


  • Mod

    Es steht dir frei, einen Cast einzusetzen. Wobei auch dort die eigentliche Konvertierung im Grunde implizit erfolgt. Dann muss man auch fragen, was denn diese l- zu rvalue-Konvertierung eigentlich macht. Und diese Frage ist gar nicht so leicht zu beantworten 🙂


Anmelden zum Antworten