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.