Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callback using lambda with closures

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?

like image 312
knick Avatar asked Sep 23 '18 03:09

knick


Video Answer


3 Answers

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.


Solution - 1

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()>;

Solution - 2

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

  1. 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();})); 
    
  2. 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:

  1. is not capable of recognizing the optional sequence of specifiers.(i.e, mutable, constexpr)

  2. 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)...); };
    
like image 74
JeJo Avatar answered Sep 27 '22 22:09

JeJo


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;
    }
};
like image 20
rafix07 Avatar answered Sep 27 '22 21:09

rafix07


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

like image 35
rmawatson Avatar answered Sep 27 '22 21:09

rmawatson