std::function mit Funktionszeiger vergleichen?



  • Ich steh' mal wieder da...

    Ich möchte einen Funktionszeiger, den ich per std::function in einer Map gespeichert habe, auf Übereinstimmung mit der Funktionsadresse testen. Folgendes Minimalbeispiel:

    #include <map>
    #include <vector>
    #include <functional>
    #include <iostream>
    
    using MBSworker = std::function<int(int x)>;
    using std::cout;
    using std::endl;
    
    std::map<int, std::map<int, MBSworker>> workerMap;
    
    int F1(int a) { return a; }
    int F2(int b) { return b; }
    
    MBSworker getWorker(int x, int y) {
      auto wmap = workerMap.find(x);
      if (wmap != workerMap.end()) {
        auto fmap = wmap->second.find(y);
        if (fmap != wmap->second.end()) {
          return fmap->second;
        }
      }
      return nullptr;
    }
    
    int main (int argc, char **argv) {
      
      workerMap[1][2] = F1;
      workerMap[2][4] = F2;
    
      if (getWorker(1, 2) == F1) {
        cout << "F1 gefunden." << endl;
      }
      return 0;
    }
    
    

    liefert diese Fehlermeldung:

    fc.cpp: In function ‘int main(int, char**)’:
    fc.cpp:31:23: error: no match for ‘operator==’ (operand types are ‘MBSworker’ {aka ‘std::function<int(int)>’} and ‘int(int)’)
       31 |   if (getWorker(1, 2) == F1) {
          |       ~~~~~~~~~~~~~~~ ^~ ~~
          |                |         |
          |                |         int(int)
          |                MBSworker {aka std::function<int(int)>}
    

    Ich muss die Funktion also irgendwie dereferenzieren, komme aber nicht auf die passende Syntax...



  • Ich habe jetzt eine (hässliche) Variante gefunden, die compiliert, aber nicht das erwartete liefert - es wird keine Gleichheit erkannt:

    #include <map>
    #include <functional>
    #include <iostream>
    
    using MBSworker = std::function<int(int x)>;
    using std::cout;
    using std::endl;
    
    std::map<int, std::map<int, MBSworker>> workerMap;
    
    int F1(int a) { return a; }
    int F2(int b) { return b; }
    
    MBSworker getWorker(int x, int y) {
      auto wmap = workerMap.find(x);
      if (wmap != workerMap.end()) {
        auto fmap = wmap->second.find(y);
        if (fmap != wmap->second.end()) {
          return fmap->second;
        }
      }
      return nullptr;
    }
    
    int main (int argc, char **argv) {
      
      workerMap[1][2] = F1;
      workerMap[2][4] = F2;
    
      if ((int(*)(int))getWorker(1, 2).target<MBSworker>() == F1) {
        cout << "F1 gefunden." << endl;
      } else {
        cout << "F1 nicht gefunden" << endl;
      }
      return 0;
    }
    


  • Dieser Beitrag wurde gelöscht!


  •   if ((int(*)(int))getWorker(1, 2).target<MBSworker>() == F1) {
        cout << "F1 gefunden." << endl;
      } else {
    

    Versuchs mal mit .target<int(*)(int)>().



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

      if ((int(*)(int))getWorker(1, 2).target<MBSworker>() == F1) {
        cout << "F1 gefunden." << endl;
      } else {
    

    Versuchs mal mit .target<int(*)(int)>().

    Nö, leider nicht. Ich habe mir mal die Adressen ausgeben lassen (target links, F1 rechts): 7ffe8f032ce0/556eefc363c9 - sind komplett anders, warum auch immer.



  • Ah, ja. Weil target() einen Zeiger auf das Target zurückgibt. D.h. in dem Fall einen Zeiger auf den Funktionszeiger. Der unnötige Cast hat das verschleiert.
    So geht's:

      auto target = getWorker(1, 2).target<int(*)(int)>();
      if (target && *target == F1) {
    


  • @Miq std::function ist kein Funktionszeiger, daher kannst du das nicht so einfach dereferenzieren.
    Wenn du die Signatur kennst, kannst du einen Pointer auf das Funktionsobjekt bekommen. Wenn da ein Funktionspointer drinnen liegt, ist das dann ein Pointer auf ein Funktionspointer.

    So sollte es gehen:

    int main(int argc, char** argv) {
    
      workerMap[1][2] = F1;
      workerMap[2][4] = F2;
      int (*const* ptr)(int) = getWorker(1, 2).target<int(*)(int)>();
    
      if (*ptr == F1) {
        cout << "F1 gefunden." << endl;
      }
      else {
        cout << "F1 nicht gefunden" << endl;
      }
      return 0;
    }
    

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

    Edit: @hustbaer war schneller und demonstriert wunderbar wie schön auto ist .



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

    Ah, ja. Weil target() einen Zeiger auf das Target zurückgibt. D.h. in dem Fall einen Zeiger auf den Funktionszeiger. Der unnötige Cast hat das verschleiert.
    So geht's:

      auto target = getWorker(1, 2).target<int(*)(int)>();
      if (target && *target == F1) {
    

    Danke, das war es! Sieht auch nicht ganz so schlimm aus 😉



  • @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.


Anmelden zum Antworten