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