Обратный вызов с использованием лямбды с замыканиями - PullRequest
0 голосов
/ 23 сентября 2018

Я пытаюсь реализовать обратный вызов, который передает управление от подпрограммы обработки прерываний функции-члену в классе c ++.Я думал, лямбда и замыкания будут удобным способом сделать это, но у меня возникают проблемы с его реализацией.Ниже приведена упрощенная версия моего кода.

Проблема, с которой я застрял, заключается в том, как сохранить " указатель функции " в " 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(); });
    }
};

Компиляциявыдает следующее сообщение об ошибке:

 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

Как изменить этот код для устранения ошибки компиляции?

Ответы [ 3 ]

0 голосов
/ 23 сентября 2018

Проблема в том, что функция enable_irq ожидает типизированного указателя функции типа void (*ExtiHandler)() , а не лямбда-функции .

Это означает, что здесь

pin.enable_irq([this]() { this->button_pressed(); });

вы пытаетесь сохранить лямбда-функцию (с захватом экземпляра) в типизированном указателе функции .Вы могли бы преобразовать лямбду в указатель на функцию (легко), если бы она была лямбда без захвата.

См. [expr.prim.lambda.closure] (сек. 7)

Тип закрытиядля неуниверсального лямбда-выражения с без лямбда-захвата , чьи ограничения (если таковые имеются) выполнены, имеет функцию преобразования в указатель на функцию с языковой связью C ++, имеющей тот же параметр ивозвращать типы в качестве оператора вызова функции типа замыкания.

Поскольку лямбда-выражения не являются просто обычными функциями , и для его захвата необходимо сохранить состояние , вы не можетенайдите любое простое или традиционное решение, чтобы назначить их указателям на функции.


Решение - 1

Самое простое решение - использовать std::function вместо этого, заплатив некоторые накладные расходы на стирание типа .Это означает, что в вашем коде просто нужно изменить

 typedef void(*ExtiHandler)();

на

typedef std::function<void()> ExtiHandler;
// or
// using ExtiHandler = std::function<void()>;

Решение - 2

Можно ли это сделатьбез использования STL?

Да .Проведя небольшое исследование по этой теме, я нашел решение для определения черт типа для хранения лямбд с закрытием в эквивалентном типизированном указателе функции.

#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);
}

Использование: СМОТРИТЕ ПРИМЕР ЖИВЫХ

  1. Теперь вы можете просто продолжить с Gpio и Buttonклассы, ничего не меняя .:

    pin.enable_irq(make_function_ptr([this]() { this->button_pressed(); })); 
    // or 
    // pin.enable_irq(make_function_ptr([&]() { this->button_pressed();})); 
    
  2. Или с аргументами.Например

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

Недостатки: Решение - 2:

  1. равно нет способен распознавать необязательную последовательность спецификаторов . (Т. Е. mutable, constexpr)

  2. не пересылки пакета параметров к чертам.то есть следующее невозможно:

    return [](Args&&... args) { return lmda(std::forward<Args>(args)...); };
    
0 голосов
/ 24 сентября 2018

Если у вас нет библиотеки std, вы можете реализовать стирание типа самостоятельно.

Примерно так ...

#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;
}

Демо

0 голосов
/ 23 сентября 2018

Объект замыкания может быть назначен указателю на функцию, только если список захвата лямбды пуст, в вашем случае это условие не выполняется - [this].

Вы можете использовать std::function в качестве оболочки для хранения ваших затворов:

#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;
    }
};
...