I'm trying to implement a callback which passes control from an Interrupt Service Routine to a member function on a c++ class. I thought lambdas and closures would be a convenient means of doing this, but I'm having trouble implementing it. Below is a simplified version of my code.
The issue I'm stuck on is how to store the "function pointer" to the "lambda".
class Gpio
{
  public:
    typedef void (*ExtiHandler)();    
  private:
    ExtiHandler handler;    
  public:
    void enable_irq(ExtiHandler handler_in) 
    { 
      // enable interrupt
      // ...
      // save handler so callback can be issued later
      handler = handler_in;
    }
};
class Button
{
  private:
    Gpio& pin;    
  public:
    Button(Gpio& pin_in) : pin(pin_in) 
    {
    };
    void button_pressed()
    {
      // do something
    }
    void init()
    {
      pin.enable_irq([this]() { this->button_pressed(); });
    }
};
Compiling fails with the following error message;
 no matching function for call to 'Gpio::enable_irq(Button::init()::<lambda()>)'candidate: void Gpio::enable_irq(Gpio::ExtiHandler) no known conversion for argument 1 from 'Button::init()::<lambda()>' to 'Gpio::ExtiHandler {aka void (*)()}' Build failed
How can I modify this code to resolve the compile error?
The problem is, that enable_irq function expects a typed function pointer of type void (*ExtiHandler)() not a lambda function.
That means, here
pin.enable_irq([this]() { this->button_pressed(); });
you are trying to store a lambda function(with capturing the instance) to a typed function pointer. You could have converted the lambda to a function pointer(easily) if it would have been a capture-less lambda.
See [expr.prim.lambda.closure] (sec 7)
The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type's function call operator.
Since lambdas are not just ordinary functions and capturing it need to preserve a state, you can not find any simple or conventional solution to make them assign to function pointers.
The simplest solution is to use std::function instead, by paying some type erasure overhead. That means, in your code, just need to change the
 typedef void(*ExtiHandler)();
to
typedef std::function<void()> ExtiHandler;
// or
// using ExtiHandler = std::function<void()>;
Can this be accomplished without using the STL?
Yes. After making a small research on this topic, I came up with a type traits solution to store the lambdas with closure to the equivalent typed function pointer.
#include <iostream>
template<typename Lambda> struct convert_lambda : convert_lambda<decltype(&Lambda::operator())> {};    
template<typename Lambda, typename ReType, typename... Args>
struct convert_lambda<ReType(Lambda::*)(Args...) const>
{
    using  funPtr = ReType(*)(Args...);
    static funPtr make_function_ptr(const Lambda& t)
    {
        static const Lambda& lmda = t;
        return [](Args... args) {   return lmda(args...);   };
    }
};    
template<typename Lambda> using convert_lambda_t = typename convert_lambda<Lambda>::funPtr;    
template<typename Lambda> constexpr convert_lambda_t<Lambda> make_function_ptr(const Lambda& t)
{
    return convert_lambda<Lambda>::make_function_ptr(t);
}
Usage: SEE LIVE EXAMPLE
You can now simply continue with your Gpio and Button classes, without
    changing anything.: 
pin.enable_irq(make_function_ptr([this]() { this->button_pressed(); })); 
// or 
// pin.enable_irq(make_function_ptr([&]() { this->button_pressed();})); 
Or with arguments. For example
int aa = 4;
auto lmda = [&aa](const int a, const float f) { std::cout << a * aa * f << std::endl; };
void(*fPtrTest)(const int, const float) = make_function_ptr(lmda);
fPtrTest(1, 2.0f);
Drawbacks: The solution - 2:
is not capable of recognizing the optional sequence of specifiers.(i.e, mutable, constexpr)
is not capable of forwarding parameter pack to the traits. i.e, the following is not possible:
return [](Args&&... args) { return lmda(std::forward<Args>(args)...); };
Closure object can be assigned to function pointer only if capture list of lambda is empty, in your case this condition is not met - [this]. 
You can use std::function as wrapper to store your closures:
#include <functional>
class Gpio
{
  public:
        using ExtiHandler = std::function<void()>;
  private:
        std::function<void()> handler;
  public:
    void enable_irq(const ExtiHandler& handler_in) 
    {
      handler = handler_in;
    }
};
                        If you don't have std library then you could implement the type erasure yourself.
Something like this ...
#include <iostream>
#include <memory>
struct function
{
    struct base
    {
    virtual void call() = 0;
    virtual base* clone() = 0;
    };
    template <typename Fn>  
    struct impl : base
    {
        Fn fn_;
    impl(Fn&& fn) : fn_(std::forward<Fn>(fn)){}
        impl(Fn& fn) : fn_(fn){}
    virtual void call()
    {
       fn_();
    }
    virtual base* clone() { return new impl<Fn>(fn_); }
    };
    base* holder_;
    function() : holder_(nullptr)
    {};
    template <typename Fn>
    function(Fn&& fn) : holder_(nullptr)
    {
    holder_ = new impl<Fn>(std::forward<Fn>(fn));
    }
    function( function&& other)
    {
    holder_ = other.holder_;
    other.holder_ = nullptr;
    }
    function(const function& other)
    {
        holder_ = other.holder_->clone();
    }
    ~function()
    {
        if (holder_) delete holder_;
    }
    function& operator=(function&& other)
    {
    if (holder_) delete holder_;
    holder_ = other.holder_;
    other.holder_ = nullptr;
    return *this;
    }
    function& operator=(const function& other)
    {
    if (holder_) delete holder_;
    holder_ = other.holder_->clone();
    return *this;
    }
    void operator()()
    {
        holder_->call();
    }
};
class Gpio
{
  public:
    using ExtiHandler = function;   
  //private:
    ExtiHandler handler;    
  //public:
    void enable_irq(ExtiHandler handler_in) 
    { 
      // enable interrupt
      // ...
      // save handler so callback can be issued later
      handler = handler_in;
    }
};
class Button
{
  private:
    Gpio& pin;    
  public:
    Button(Gpio& pin_in) : pin(pin_in) 
    {
    };
    void button_pressed()
    {
      std::cout << "Button pressed" << std::endl;
    }
    void init()
    {
      pin.enable_irq([this]() { this->button_pressed(); });
    }
};
int main() {
    Gpio some_pin;
    Button b(some_pin);
    b.init();
    some_pin.handler();
    return 0;
}
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With