pjueon/JetsonGPIO

Unable to use class methods as callbacks

DanielGM279 opened this issue · 4 comments

Hello, I'm trying to implement 2 magnetic encoders into my project. In order to do so I've created a script that takes two input pins, adds an interrupt to each of the pins and counts pulses. That works fine when doing inside the main function. The problem is whenever I try to fit all of this inside a class. Right now I have the following code:

class EncReader
{
private:
    int EncA;
    int EncB;
    int Pulse;
public:
    EncReader(int new_EncA, int new_EncB, double new_ts);
    void cb_fcnA(const string &channel);
    void cb_fcnB(const string &channel);
    void pulse_callback_encA();
    void pulse_callback_encB();
    void encoder_setup();
    int get_pulse();
};

EncReader::EncReader(int new_EncA, int new_EncB, double new_ts):
    EncA(new_EncA), EncB(new_EncB), Pulse(0), ts(new_ts)

{
    encoder_setup();
}

void EncReader::encoder_setup(){
    setmode(BOARD);
    setup(EncA,IN);
    setup(EncB,IN);

    add_event_detect(EncA,BOTH, cb_fcnA);
    add_event_detect(EncB,BOTH, cb_fcnB);
}

void EncReader::cb_fcnA(const string &channel){
    pulse_callback_encA();
}

void EncReader::cb_fcnB(const string &channel){
    pulse_callback_encB();
}

void EncReader::pulse_callback_encA(){             //Interrupt attached to encoder A
    if(input(EncB) == input(EncA)){
        Pulse--;
    }
    else{
        Pulse++;
    }
}

void EncReader::pulse_callback_encB(){             //Interrupt attached to encoder B
    if(input(EncA) != input(EncB)){
        Pulse--;
    }
    else{
        Pulse++;
    }
}

However, I get the following error when trying to compile:

error: invalid use of non-static member function ‘void EncReader::cb_fcnA(const string&)'
add_event_detect(EncA,BOTH, cb_fcnA);

I need to use these functions inside of the class for my project, so I don't know what else to do. Any help is appreciated.

You cannot pass the method name(non-static method) of the class as an argument like that in C++.
Because the compiler cannot decide which instance should be used for the method in that way.

In general, you can use lambda expression to pass an non-static member function as an argument (by capturing "this" pointer),
but you cannot do that in this case because the library requires that the callbacks must be equaility-comparable.

So in your case, you should make wrappers for your callbacks.
Here is an expample:

#include <functional>

// alias
using func_t = std::function<void(const std::string&)>;

// define a wrapper class for the callback 
class CallbackWrapper
{
public:
    CallbackWrapper(const func_t& func, const std::string& id) 
     : func(func), id(id)
    {
    }

    // copy-constructable
    CallbackWrapper(const CallbackWrapper&) = default;

    // equality-comparable
    bool operator==(const CallbackWrapper& other) const
    {
        return other.id == id;
    }

private:
    func_t func;
    std::string id;   // id for equality-comparing
};

// ...

void EncReader::encoder_setup(){
    setmode(BOARD);
    setup(EncA,IN);
    setup(EncB,IN);

    // Using lambda as the first argument. You can see that "this" pointer is captured in the lambda expressions.
    // When the lambda obejct is invoked, the member functions of "this" object will be called.  
    // The second argument is a id for each callback which will be used for equality-comparing
    CallbackWrapper callbackA([this](const std::string& channel){ pulse_callback_encA(); }, "encA");
    CallbackWrapper callbackB([this](const std::string& channel){ pulse_callback_encB(); }, "encB");

    add_event_detect(EncA, BOTH, callbackA);
    add_event_detect(EncB, BOTH, callbackB);
}

If you're not familiar with the modern C++ synyax and don't know what lambda expression is, please check this first.

Thank you for your response. I've been reading about lambda expressions and now I understand the need to use it along with a Callback Wrapper class.

However, there seems to be an issue when trying it out

/usr/local/include/JetsonGPIO.h:199: error: no matching function for call to ‘std::function<void(const std::__cxx11::basic_string&)>::function(CallbackWrapper&)’
comparer([](const func_t& A, const func_t& B) { return comparer_impl<std::decay_t>(A, B); })

Which I don't know if you know how to fix.

Thanks in advance.

‘std::function<void(const std::__cxx11::basic_string&)>

Oops, my bad. I forgot the most important part: CallbackWrapper object must be callable.
So you have to add void operator()(const std::string&) to the CallbackWrapper class definition.

// define a wrapper class for the callback 
class CallbackWrapper
{
public:
    // alias
    using func_t = std::function<void(const std::string&)>;

    CallbackWrapper(const func_t& func, const std::string& id) 
     : func(func), id(id)
    {
    }

    // copy-constructable
    CallbackWrapper(const CallbackWrapper&) = default;

    // equality-comparable
    bool operator==(const CallbackWrapper& other) const
    {
        return other.id == id;
    }

    // callable
    void operator()(const std::string& channel) const
    {
        if(func != nullptr)
            func(channel);
    }

private:
    func_t func;
    std::string id;   // id for equality-comparing
};


// ...

void EncReader::encoder_setup(){
    setmode(BOARD);
    setup(EncA,IN);
    setup(EncB,IN);

    // Using lambda as the first argument. You can see that "this" pointer is captured in the lambda expressions.
    // When the lambda obejct is invoked, the member functions of "this" object will be called.  
    // The second argument is a id for each callback which will be used for equality-comparing
    CallbackWrapper callbackA([this](const std::string& channel){ pulse_callback_encA(); }, "encA");
    CallbackWrapper callbackB([this](const std::string& channel){ pulse_callback_encB(); }, "encB");

    add_event_detect(EncA, BOTH, callbackA);
    add_event_detect(EncB, BOTH, callbackB);
}

Thank you so much, it works like a charm now. I'll close the issue now.