std::function mit Funktionszeiger vergleichen?



  • @Schlangenmensch sagte in std::function mit Funktionszeiger vergleichen?:

    Gibt es einen konkreten Use case? Vlt gibt es für dein eigentliches Problem eine schönere Lösung.

    Das ist eigentlich eine nicht vorgesehene Nutzung. Die Funktionen (Callbacks übrigens) werden in einer Library in der Map Funktionsnummern zugeordnet, und normalerweise darüber gesucht und dann ausgeführt. Hier will ich in einem Testprogramm prüfen, ob das Matching die richtigen Funktionen zurückliefert - die Beziehung ist etwas komplexer, als nur 1:1 über Funktionsnummern, aber das führt hier wohl etwas weit. Normalerweise hätte ich die Map mit einem zusätzlichen Kennzeichen zur Funktion erweitert und das abgefragt, aber so etwas würde bei der eigentlichen Anwendung nur Overhead bedeuten.



  • Die Übertragung ins echte Leben funktioniert leider irgendwie doch nicht, ich bekomme an der nämlichen Stelle:

    Test/main.cpp:1620:42: error: 'using MBSworker = class std::function<ModbusMessage(ModbusMessage)>' {aka 'class std::function<ModbusMessage(ModbusMessage)>'} has no member named 'target'
         auto wrk = RTUserver.getWorker(1, 3).target<ModbusMessage(*)(ModbusMessage)>();
                                              ^~~~~~
    

    Die Definition vom MBSworkerenthält übrigens class nicht explizit:

    using MBSworker = std::function<ModbusMessage(ModbusMessage msg)>;
    

    getWorker() ist eine Memberfunktion des Objektes RTUserver, wenn das eine Rollen spielen sollte.

    Das ganze läuft auf einem ESP32 mit Arduino-Core - wenn ich Pech habe, ist target da nicht implementiert worden.



  • Selbstkommentar: man muss mit Compilerflag -frtti übersetzen!



  • Ja, .target() braucht RTTI, damit es prüfen kann ob der Typ eh übereinstimmt 🙂



  • @hustbaer sagte in std::function mit Funktionszeiger vergleichen?:

    Ja, .target() braucht RTTI, damit es prüfen kann ob der Typ eh übereinstimmt 🙂

    Was natürlich für diesen speziellen Anwendungsfall ein klein wenig ineffizienter ist, als nur ein Pointer-Vergleich. Da die Adressen ja eindeutig sind, wäre die Typprüfung beim Vergleich mit der Adresse einer existierenden Funktion quasi implizit. Der Check macht allerdings Sinn, wenn man target() tatsächlich aufrufen will.

    Ich sehe aber nicht, wie das mit dem Interface von std::function anders ginge. Ich frage mich allerdings, weshalb std::function::operator== offenbar nur mit nullptr und nicht einer anderen Funktion vergleichen kann. Dort hätte man das m.E. effizient implementieren können, auch ohne ungeprüfte Funktionspointer an den Anwender herauszugeben.



  • @Miq Ist jetzt schon ein paar Tage her, sorry.
    Wenn es darum geht, den Suchprozess testbar zu machen, wäre es vlt schöner, wenn man den Suchprozess unabhängig von den tatsächlichen Funktionen testen kann.

    Man könnte z.B. eine Klasse implementieren, die das Suchen der Funktion übernimmt und eine Klasse, die die Funktionen bereit stellt. Die Klasse, die das Suchen implementiert, bekommt ein Objekt der Klasse, die die Funktionen implementiert, übergeben (dependency injection).

    Dann kann man für den Test, die aufzurufenden Funktionen mocken, z.b. mit gMock (https://google.github.io/googletest/gmock_for_dummies.html), und dann im Testfall überprüfen, dass die richtige Funktione aufgerufen wurde.



  • @Finnegan sagte in std::function mit Funktionszeiger vergleichen?:

    Was natürlich für diesen speziellen Anwendungsfall ein klein wenig ineffizienter ist, als nur ein Pointer-Vergleich. Da die Adressen ja eindeutig sind, wäre die Typprüfung beim Vergleich mit der Adresse einer existierenden Funktion quasi implizit. Der Check macht allerdings Sinn, wenn man target() tatsächlich aufrufen will.

    Das Target kann ja einen beliebigen Typ haben. Da können alle mögliche Dinge erfordern dass man den genauen Typ kennt.

    Ich frage mich allerdings, weshalb std::function::operator== offenbar nur mit nullptr und nicht einer anderen Funktion vergleichen kann. Dort hätte man das m.E. effizient implementieren können, auch ohne ungeprüfte Funktionspointer an den Anwender herauszugeben.

    Wie willst du das effizienter implementieren?



  • @hustbaer sagte in std::function mit Funktionszeiger vergleichen?:

    Wie willst du das effizienter implementieren?

    Ich dachte einfach intern vergleichen ob die beiden std::function-Objekte auf die selbe Speicheradresse verweisen. Ohne Typprüfung. Entweder der selbe Function Pointer oder das selbe Funktionsobjekt, Lambda, etc. Die Adressen sind ja eindeutig.

    Ist aber nur minimalst effizenter. Man spart sich halt das typeid() zur Laufzeit.



  • @Finnegan sagte in std::function mit Funktionszeiger vergleichen?:

    @hustbaer sagte in std::function mit Funktionszeiger vergleichen?:

    Wie willst du das effizienter implementieren?

    Ich dachte einfach intern vergleichen ob die beiden std::function-Objekte auf die selbe Speicheradresse verweisen. Ohne Typprüfung. Entweder der selbe Function Pointer oder das selbe Funktionsobjekt, Lambda, etc. Die Adressen sind ja eindeutig.

    Verstehe ich nicht. Bei Funktionszeigern würde das funktionieren, ja. Aber nicht bei Funktionsobjekten. Es wird ja eine Kopie des Targets im std::function gespeichert, also eine Kopie des Funktionszeigers bzw. eben Funktionsobjects. Und komplexe Objekte kannst du nicht einfach mit memcpy vergleichen. D.h. du musst erst wieder den Typ kennen, um den passenden operator == aufzurufen.



  • @hustbaer sagte in std::function mit Funktionszeiger vergleichen?:

    @Finnegan sagte in std::function mit Funktionszeiger vergleichen?:

    @hustbaer sagte in std::function mit Funktionszeiger vergleichen?:

    Wie willst du das effizienter implementieren?

    Ich dachte einfach intern vergleichen ob die beiden std::function-Objekte auf die selbe Speicheradresse verweisen. Ohne Typprüfung. Entweder der selbe Function Pointer oder das selbe Funktionsobjekt, Lambda, etc. Die Adressen sind ja eindeutig.

    Verstehe ich nicht. Bei Funktionszeigern würde das funktionieren, ja. Aber nicht bei Funktionsobjekten. Es wird ja eine Kopie des Targets im std::function gespeichert, also eine Kopie des Funktionszeigers bzw. eben Funktionsobjects. Und komplexe Objekte kannst du nicht einfach mit memcpy vergleichen. D.h. du musst erst wieder den Typ kennen, um den passenden operator == aufzurufen.

    Man kann durchaus argumentieren, dass ein kopiertes Funktionobjekt eine andere Funktion ist. Die würden halt immer ungleich sein und das ganze tatsächlich nur mit Funktionszeigern sinnvoll funktionieren. "Logisch kaputt" wäre das nicht, nur eben für Funktionsobjekte nur bedingt hilfreich 😉



  • @Finnegan Ja, könnte man. Dann wäre es aber besser, den operator == gleich auf Funktionszeiger einzuschränken.

    "Logisch kaputt" wäre das nicht, nur eben für Funktionsobjekte nur bedingt hilfreich

    Doch, meiner Meinung nach schon. Was gleich ist und was nicht sollte immer noch der Typ, bzw. die dazugehörigen freien Operatoren entscheiden, und nicht std::function.



  • @hustbaer sagte in std::function mit Funktionszeiger vergleichen?:

    @Finnegan Ja, könnte man. Dann wäre es aber besser, den operator == gleich auf Funktionszeiger einzuschränken.

    "Logisch kaputt" wäre das nicht, nur eben für Funktionsobjekte nur bedingt hilfreich

    Doch, meiner Meinung nach schon. Was gleich ist und was nicht sollte immer noch der Typ, bzw. die dazugehörigen freien Operatoren entscheiden, und nicht std::function.

    Ja, das macht durchaus auch Sinn. Mir gings auch nur darum, das Fitzelchen an typeid()-Overhead einzusparen, das man speziell bei Funktionspointern wegen der eindeutigen Adresse nicht braucht. Möglicherweise ist das aber nicht wirklich die Mühe wert.

    Alternativ hätte std::function::operator== (der nur mit nullptr vergleichen kann) auch Vergleiche zwischen zwei std::function erlauben können, wenn zwischen deren Callable-Typen ein operator== definiert ist. Für alles andere wäre der Operator dann nicht definiert. Im Zweifel könnte der User den operator== dann als freie Funktion einfach selbst definieren und z.B. solche Späße umsetzen wie Funktionspointer auch mit inkompatibler Signatur ausschliesslich über die Adresse zu vergleichen.



  • @Finnegan sagte in std::function mit Funktionszeiger vergleichen?:

    Alternativ hätte std::function::operator== (der nur mit nullptr vergleichen kann) auch Vergleiche zwischen zwei std::function erlauben können, wenn zwischen deren Callable-Typen ein operator== definiert ist.

    Wie soll das gehen?
    Bei der Zuweisung weiss der Compiler ja nicht dass dann später evtl. mit einem std::function verglichen wird, und schon gar nicht was für ein Typ in diesem anderen std::function dann stecken wird.

    Maximal könnte man noch Vergleiche erlauben wenn beide std::function den exakt selben Typ enthalten. Nur sogar da hätte man einen blöden Fall den man erst zur Laufzeit erkennt. Nämlich wenn die zwei std::function zwar den selben Typ enthalten, dieser aber gar nicht mit sich selbst vergleichbar ist. Das müsste man dann zur Laufzeit behandeln. false zurückgeben? Exception werfen? Universum zerstören?

    Insgesamt würden sich Vergleiche mit std::function also deutlich anders verhalten, als Vergleiche der gespeicherten Typen. Und ich denke genau das ist der Grund warum std::function diese Vergleiche nicht implementiert.

    Als Ersatz dafür gibt es halt target(). Damit kann man dann bestimmte Vergleiche selbst implementieren. Das vermeidet Überraschungen.



  • @hustbaer Oh Mann! Danke für die hartnäckige Nachhilfe. Jetzt ist bei mir endlich der Groschen gefallen, dass std::function zwar die Funktionssignatur in ihrem Typ speichert, aber dennoch eine Type Erasure macht und zur Laufzeit an verschiedene Typen binden kann, welche die selbe Signatur unterstützen. Ich hätte mich ja auch fragen können, warum überhaupt mit typeid() gearbeitet wird. Sorry, manchmal bin ich echt ein Holzkopf 😳 ... ich dachte echt die kennt ihre statischen Typen und könnte einfach zur Compile-Zeit schauen, ob ein operator==(T_LHS, T_RHS) existiert.


Anmelden zum Antworten