c++teensy

Pass class function to another class function


sorry for possible duplicates, but I didn't understand the examples and codes snippets I found.

I have a class named "EncoderWrapper" which includes some functions. One of these functions is called "onAfterTouch" and is declared in the "EncoderWrapper.h" file.

void onAfterTouch(byte channel, byte pressure);

The functions will become a callback for another class function of a library I use

inline void setHandleAfterTouch(void (*fptr)(uint8_t channel, uint8_t pressure)) {                
    usb_midi_handleAfterTouch = fptr;
};

Note: I'm totally new to C++, so I want to say sorry if I'm doing some "no-gos" or mixing up some terms.

The question is: How can I pass my class function (member function?) to that "setHandleAfterTouch" function of the library?

This won't work:

void EncoderWrapper::attachMIDIEvents()
{
    usbMIDI.setHandleAfterTouch(&EncoderWrapper::onAfterTouch);
}

... my IDE says

no matching function for call usb_midi_class:setHandleAfterTouch(void (EncoderWrapper::*)(byte, byte))

I've also tried

usbMIDI.setHandleAfterTouch((&this->onAfterTouch));

But this won't work ... and I don't get the approach on that.

Every Help is very appreciated ;-)


Solution

  • Function pointer and member function pointer have different types. You can it for yourself:

    struct Test {
        void fun();
    };
    
    int main() {
        void(*ptr)() = &Test::fun; // error!
    }
    

    Instead, member function pointer need this syntax:

    void(Test::*fun)() = &Test::fun; // works!
    

    Why you ask? Because member function need an instance to be called with. And calling that function have a special syntax too:

    Test t;
    
    (t.*funptr)();
    

    To accept member function pointer, you'll need to change your code to this:

    inline void setHandleAfterTouch(void(EncodeWrapper::*fptr)(uint8_t, uint8_t)) {                
        usb_midi_handleAfterTouch = fptr;
    };
    

    Since it's rather limiting accepting only the functions from one class, I recommend using std::function:

    inline void setHandleAfterTouch(std::function<void(uint8_t, uint8_t)> fptr) {                
        usb_midi_handleAfterTouch = std::move(fptr);
    };
    

    This will allow you to send lambda with captures, and call your member function insode it:

    //  we capture this to use member function inside
    //                           v---
    usbMIDI.setHandleAfterTouch([this](uint8_t, channel, uint8_t pressure) {
        onAfterTouch(channel, pressure);
    });
    

    It seems you can't change, and by looking quickly at the API, it doesn't seem you have access to a state object.

    In that case, if you want to use your member function, you need to introduce a global state:

    // global variable
    EncodeWrapper* encode = nullptr;
    
    // in your function that sets the handle
    encode = this; //            v--- No capture makes it convertible to a function pointer
    usbMIDI.setHandleAfterTouch([](uint8_t, channel, uint8_t pressure) {
        encode->onAfterTouch(channel, pressure);
    });
    

    Another solution would be to make onAfterTouch function static. If it's static, it's pointer is not a member function pointer, but a normal function pointer.