Pointer auf template-Memberfunktion?



  • Ich habe ein typedef für eine Callbackfunktion, die aus einer Klasseninstanz aufgerufen wird:

    typedef void (*MBOnError) (Modbus::Error, uint32_t token);
    

    Die aufzurufende Funktion wird so bei der Klasse eingetragen:

    void onErrorHandler(MBOnError handler); 
    

    und dort dann in einer Variablen gespeichert: MBOnError onError;. Der Aufruf erfolgt dann mit:

    if (instance->onError) {
      instance->onError(response->getError(), request->getToken());
    }
    

    Klappt auch gut, wenn ich eine reguläre Funktion da eintrage:

    void handleError(Error error, uint32_t token) 
    {
      // ModbusError wraps the error code and provides a readable error message for it
      ModbusError me(error);
      Serial.printf("Error response: %02X - %s token: %d\n", (int)me, (const char *)me, token);
    }
    ...
    MB.onErrorHandler(&handleError);
    

    Wenn ich aber das Gleiche mit einer Memberfunktion einer template class versuche, gibt es Ärger.
    Die Funktion:

    template<typename SERVERCLASS>
    void ModbusBridge<SERVERCLASS>::bridgeErrorHandler(Error error, uint32_t token) {
      ResponseBuf *responseBuffer = (ResponseBuf *)token;
      responseBuffer->data.push_back(error);
      responseBuffer->ready = true;
    }
    

    und der Versuch der Eintragung:

    client->onErrorHandler(reinterpret_cast<MBOnError>(&ModbusBridge<SERVERCLASS>::bridgeErrorHandler));
    

    (Der Cast ist mein hilfloser Versuch, das zu erzwingen) Die Meldung dazu ist:

    converting from 
    'void (ModbusBridge<ModbusServerTCP<WiFiServer, WiFiClient> >::*)(Modbus::Error, uint32_t) {aka void (ModbusBridge<ModbusServerTCP<WiFiServer, WiFiClient> >::*)(Modbus::Error, unsigned int)}' 
    to 
    'MBOnError {aka void (*)(Modbus::Error, unsigned int)}' [-Wpmf-conversions]
    

    Es muss doch eine warnungsfreie Methode geben, das zu bewerkstelligen?

    Die template-Klasse scheint den Compiler davon abzuhalten zu erkennen, dass die Funktion die korrekte Signatur hat.



  • mcve.



  • Selbstantwort: zum Glück kann ich die Callbackfunktion in der template class auch als static deklarieren - dann geht es mit

    client->onErrorHandler(&(this->bridgeErrorHandler));
    


  • @Swordfish sagte in Pointer auf template-Memberfunktion?:

    mcve.

    Ja. Beim nächsten Mal 😉



  • @Miq sagte in Pointer auf template-Memberfunktion?:

    zum Glück kann ich die Callbackfunktion in der template class auch als static deklarieren - dann geht es mit

    und was ist dann der Unterschied zu einer freien Funktion? <°>>>()



  • @Swordfish sagte in Pointer auf template-Memberfunktion?:

    @Miq sagte in Pointer auf template-Memberfunktion?:

    zum Glück kann ich die Callbackfunktion in der template class auch als static deklarieren - dann geht es mit

    und was ist dann der Unterschied zu einer freien Funktion? <°>>>()

    Dass sie - da protected - nur innerhalb der template class verwendet werden kann, wie es auch sein sollte?



  • Dein typedef ist an keiner Klasse gebunden. Liegt also nicht daran dass du ein Template benutzt, sondern ein Objekt. Versuchs mal mit

    typedef void (ModbusBridge<SERVERCLASS>::*MBOnError) (Modbus::Error, uint32_t token);
    

    @Miq sagte in Pointer auf template-Memberfunktion?:

    Selbstantwort: zum Glück kann ich die Callbackfunktion in der template class auch als static deklarieren - dann geht es mit

    client->onErrorHandler(&(this->bridgeErrorHandler));
    

    Eine statische Funktion ist an keinem Objekt gebunden. Hierfür hast du dann wieder die alte Signatur.

    Gibt es einen Grund wieso du nicht vom <functional> Header Gebrauch machst?

    https://en.cppreference.com/w/cpp/utility/functional/function



  • @eigenartig sagte in Pointer auf template-Memberfunktion?:

    Dein typedef ist an keiner Klasse gebunden. Liegt also nicht daran dass du ein Template benutzt, sondern ein Objekt. Versuchs mal mit

    typedef void (ModbusBridge<SERVERCLASS>::*MBOnError) (Modbus::Error, uint32_t token);
    

    Eine statische Funktion ist an keinem Objekt gebunden. Hierfür hast du dann wieder die alte Signatur.

    Danke für die Erklärung - ich verstehe das jetzt, glaube ich. Allerdings ist der Aufruf mit einer freien Funktion hier der Normalfall, der Aufruf aus der template class nutzt die Callbackschnittstelle intern. Wenn ich das typedef ändere, breche ich das reguläre Interface.

    Gibt es einen Grund wieso du nicht vom <functional> Header Gebrauch machst?

    Ja: ich habe das noch nie benutzt, weil ich bis vor kurzem noch C++98 als Standard verwendet habe und mich jetzt so langsam wenigstens in C++11 einarbeite... 😱
    Schaue ich mir jetzt mal an!



  • Hmmmh... Jetzt hänge ich an einer Variante des gleichen Problems fest.

    Aufgabenstellung: Ich habe jeweils eine von mehreren Basisklassen, die alle u.a. einen Funktionspointer als Callback enthalten, von der eine Templateklasse abgeleitet wird. Die sieht immer gleich aus, nur die Basisklassen unterscheiden sich intern. Diese abgeleitete Templateklasse soll jetzt eine eigene Funktion - die nicht static sein kann - an den Funktionspointer hängen:

    #include <functional>
    
    using FPTR = std::function<int(int)>;
    
    class A {
    public:
      FPTR worker;
      int i;
      A(int x) { i = x; }
    };
    
    template <class BASE>
    class C : public BASE {
    public:
      C(int y) : BASE(y) {
        this->worker = C<BASE>::doit;  // <==== hier hakt es!
      }
      int doit(int z) { return this->i + z; }
      int doit_differently(int z) { return this->i - z; }
    };
    
    int main() {
      C<A> instance(66);
      instance.worker(5);
      return 0;
    }
    

    Hier zum Ausprobieren: IDEone.
    Das gibt bei der Zuweisung diesen Fehler:

    prog.cpp: In constructor ‘C<BASE>::C(int)’:
    prog.cpp:16:26: error: invalid use of non-static member function ‘int C<BASE>::doit(int) [with BASE = A]’
         this->worker = C<BASE>::doit;
                              ^~~~
    
    • was klar ist, denn C<BASE>::doit ist die Funktion selbst, kein Pointer darauf. Verwende ich aber &C<BASE>::doit, kommt das hier:
    prog.cpp: In instantiation of ‘C<BASE>::C(int) [with BASE = A]’:
    prog.cpp:23:19:   required from here
    prog.cpp:16:18: error: no match for ‘operator=’ (operand types are ‘FPTR’ {aka ‘std::function<int(int)>’} and ‘int (C<A>::*)(int)’)
         this->worker = &C<BASE>::doit;
         ~~~~~~~~~~~~~^~~~~~~
    In file included from /usr/include/c++/8/functional:59,
                     from prog.cpp:1:
    /usr/include/c++/8/bits/std_function.h:461:7: note: candidate: ‘std::function<_Res(_ArgTypes ...)>& std::function<_Res(_ArgTypes ...)>::operator=(const std::function<_Res(_ArgTypes ...)>&) [with _Res = int; _ArgTypes = {int}]’
           operator=(const function& __x)
           ^~~~~~~~
    /usr/include/c++/8/bits/std_function.h:461:7: note:   no known conversion for argument 1 from ‘int (C<A>::*)(int)’ to ‘const std::function<int(int)>&’
    /usr/include/c++/8/bits/std_function.h:479:7: note: candidate: ‘std::function<_Res(_ArgTypes ...)>& std::function<_Res(_ArgTypes ...)>::operator=(std::function<_Res(_ArgTypes ...)>&&) [with _Res = int; _ArgTypes = {int}]’
           operator=(function&& __x) noexcept
           ^~~~~~~~
    /usr/include/c++/8/bits/std_function.h:479:7: note:   no known conversion for argument 1 from ‘int (C<A>::*)(int)’ to ‘std::function<int(int)>&&’
    /usr/include/c++/8/bits/std_function.h:493:7: note: candidate: ‘std::function<_Res(_ArgTypes ...)>& std::function<_Res(_ArgTypes ...)>::operator=(std::nullptr_t) [with _Res = int; _ArgTypes = {int}; std::nullptr_t = std::nullptr_t]’
           operator=(nullptr_t) noexcept
           ^~~~~~~~
    /usr/include/c++/8/bits/std_function.h:493:7: note:   no known conversion for argument 1 from ‘int (C<A>::*)(int)’ to ‘std::nullptr_t’
    /usr/include/c++/8/bits/std_function.h:522:2: note: candidate: ‘template<class _Functor> std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_U1>::type>, std::function<_Res(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Functor = _Functor; _Res = int; _ArgTypes = {int}]’
      operator=(_Functor&& __f)
      ^~~~~~~~
    /usr/include/c++/8/bits/std_function.h:522:2: note:   template argument deduction/substitution failed:
    /usr/include/c++/8/bits/std_function.h: In substitution of ‘template<class _Functor> std::function<int(int)>::_Requires<std::function<int(int)>::_Callable<typename std::decay<_Tp>::type, typename std::result_of<typename std::decay<_Tp>::type&(int)>::type>, std::function<int(int)>&> std::function<int(int)>::operator=<_Functor>(_Functor&&) [with _Functor = int (C<A>::*)(int)]’:
    prog.cpp:16:18:   required from ‘C<BASE>::C(int) [with BASE = A]’
    prog.cpp:23:19:   required from here
    /usr/include/c++/8/bits/std_function.h:522:2: error: no type named ‘type’ in ‘class std::result_of<int (C<A>::*&(int))(int)>’
    prog.cpp: In instantiation of ‘C<BASE>::C(int) [with BASE = A]’:
    prog.cpp:23:19:   required from here
    /usr/include/c++/8/bits/std_function.h:531:2: note: candidate: ‘template<class _Functor> std::function<_Res(_ArgTypes ...)>& std::function<_Res(_ArgTypes ...)>::operator=(std::reference_wrapper<_Functor>) [with _Functor = _Functor; _Res = int; _ArgTypes = {int}]’
      operator=(reference_wrapper<_Functor> __f) noexcept
      ^~~~~~~~
    /usr/include/c++/8/bits/std_function.h:531:2: note:   template argument deduction/substitution failed:
    prog.cpp:16:18: note:   mismatched types ‘std::reference_wrapper<_Tp>’ and ‘int (C<A>::*)(int)’
         this->worker = &C<BASE>::doit;
         ~~~~~~~~~~~~~^~~~~~~
    

    Verstehe ich im Prinzip auch, aber: wie kann ich die Aufgabenstellung lösen?



  • this->worker = std::bind(&C<BASE>::doit, this, std::placeholders::_1);
    

    ist die Lösung!
    Um eine Memberfunktion aufzurufen, benötigt man ja ein Objekt der Klasse (hier also this) und std::placeholders::_1 ist ein Platzhalter für den zu übergebenen Parameter.

    PS: Eine andere Alternative wäre eine Lambda-Funktion:

    this->worker  = [this](z) { C<BASE>::doit(z); }
    


  • @Th69 sagte in Pointer auf template-Memberfunktion?:

    this->worker = std::bind(&C<BASE>::doit, this, y);

    Klasse! Ich habe mit allen Varianten von this probiert, aber auf std::bind bin ich nicht gekommen... 🤦♂

    Danke! 💚



  • Ich habe meinen Beitrag noch mal editiert (hatte mich bzgl. y vertan).



  • @Miq sagte in Pointer auf template-Memberfunktion?:

    aber auf std::bind bin ich nicht gekommen.

    Benutze Lamdas, nicht std::bind.



  • @Miq sagte in Pointer auf template-Memberfunktion?:

    Klasse! Ich habe mit allen Varianten von this probiert, aber auf std::bind bin ich nicht gekommen...

    Hättest du das von mir verlinkte Beispiel gelesen wärest du wohl schneller drauf gekommen. Oder auch nicht. Kannst du Englisch?

    cppreference gibts auch auf Deutsch leider fehlt da aber der <functional> Header.



  • @Miq sagte in Pointer auf template-Memberfunktion?:

    @Swordfish sagte in Pointer auf template-Memberfunktion?:

    mcve.

    Ja. Beim nächsten Mal 😉

    @Miq sagte in Pointer auf template-Memberfunktion?:

    Hmmmh... Jetzt hänge ich an einer Variante des gleichen Problems fest.

    Wer nicht hören will ...



  • @Swordfish sagte in Pointer auf template-Memberfunktion?:

    Wer nicht hören will ...

    Wie meinst Du das? Diesmal gab es doch ein minimales Beispiel, an dem man das Problem sehen konnte?



  • @eigenartig sagte in Pointer auf template-Memberfunktion?:

    Hättest du das von mir verlinkte Beispiel gelesen wärest du wohl schneller drauf gekommen. Oder auch nicht. Kannst du Englisch?

    cppreference gibts auch auf Deutsch leider fehlt da aber der <functional> Header.

    Ja, Englisch kann ich, ich habe aber konstant Probleme damit, cppreference zu verstehen. Das ist irgendwie ein Level zu hoch für mich, deswegen suche ich meist nach weiteren Beispielanwendungen.

    Wie gesagt, das C++, das ich mal gelernt habe, war C++98, danach habe ich mich jahrelang nicht mehr damit befasst. Insofern ist C++11 und neueres komplettes Neuland für mich. Aber man lernt ja so nach und nach 😉



  • @Miq sagte in Pointer auf template-Memberfunktion?:

    Wie meinst Du das? Diesmal

    ja, das hättest Du gleich bringen können.



  • @Miq sagte in Pointer auf template-Memberfunktion?:

    Ja, Englisch kann ich, ich habe aber konstant Probleme damit, cppreference zu verstehen. Das ist irgendwie ein Level zu hoch für mich, deswegen suche ich meist nach weiteren Beispielanwendungen.

    cppreference nutzt auch meist eine Sprache und viele Formulierungen, die sich genau so im C++-Standard finden. Das finde ich auch nicht immer leicht zu verstehen. Es lohnt sich aber es doch immer mal wieder zu versuchen. Die Sprache ist nämlich sehr präzise und führt zu einem tieferen Verständnis, wenn man es erstmal begriffen hat.


Log in to reply