Home > front end >  How to create a function that takes both function pointers and lambda as arguments?
How to create a function that takes both function pointers and lambda as arguments?

Time:01-30

I have the following class who has a method called errorHandler that needs to use several different callbacks:

class IOPin;
class IOPinHandler
{
    IOPinHandler();
    virtual ~IOPinHandler();

    static bool ptrFun(IOPinHandler&) { return true; };

    template<typename T>
    bool init(IOPin& obj, const T& param);

    template<typename HandlerReturn = void, typename ...Args>
    HandlerReturn errorHandler(const GpioStatusCode& code, HandlerReturn(*callback)(IOPinHandler& obj, const Args&...), const Args&... args);

    // Added this overload to support passing lambdas as arguments, 
    // however does not seems to be called
    template<typename HandlerReturn = void, typename ...Args>
    HandlerReturn errorHandler(const GpioStatusCode& code, const std::function<HandlerReturn(IOPinHandler&, const Args&...)>& callback, Args&... args);
};

I can use the following:

return errorHandler(GpioStatusCode::wrongArgumentsError, &IOPinHandler::ptrFun);

And the code compiles successfully and runs as I expect it to do.


I can also use pass a lambda function in the following way:

auto fix = [](IOPinHandler& obj) -> bool 
{
    return true; 
};
return errorHandler(GpioStatusCode::wrongArgumentsError, static_cast<bool(*)(IOPinHandler&)>(fix));

This way, the code also compiles and runs successfully.


However, if I add capture to the lambda function, the code won't compile:

auto fix = [&](IOPinHandler& obj) -> bool 
{
    return true; 
};
return errorHandler(GpioStatusCode::wrongArgumentsError, fix);

I get the following compile time error:

error: no matching function for call to 'IOPinHandler::errorHandler(GpioStatusCode, IOPinHandler::init<GpioMode>::<lambda(IOPinHandler&)>&)'
   12 |         return errorHandler(GpioStatusCode::wrongArgumentsError, fix);

I thought that adding:

template<typename HandlerReturn = void, typename ...Args>
HandlerReturn errorHandler(const GpioStatusCode& code, const std::function<HandlerReturn(IOPinHandler&, const Args&...)>& callback, Args&... args);

Would match the template instantiation for receiving the lambda function as argument, but the fact that I have to cast the lambda without capture and that the lambda with capture does not work either, kinda suggest to me that only the first definition or "errorHandler" is being invoked.

Is my reasoning right? How could I get pass this?


Edit: Full code.

#include <functional>

class IOPin;

class IOPinHandler
{

    public:
        template<typename ...T>
        IOPinHandler(const T&... args);
        virtual ~IOPinHandler(); 

        static bool ptrFun(IOPinHandler&);

        template<typename T>
        bool init(const T& param);

        template<typename HandlerReturn = void, typename ...Args>
        HandlerReturn errorHandler(const int& code, HandlerReturn(*callback)(IOPinHandler& obj, const Args&...), const Args&... args);

        // Added this overload to support passing lambdas as arguments, 
        // however does not seems to be called
        template<typename HandlerReturn = void, typename ...Args>
        HandlerReturn errorHandler(const int& code, const std::function<HandlerReturn(IOPinHandler&, const Args&...)>& callback, Args&... args);
};

template<typename ...T>
IOPinHandler::IOPinHandler(const T&... args)
{
    (init(args),...);
}

bool IOPinHandler::ptrFun(IOPinHandler&)
{
    return true;
}

template<typename T>
bool IOPinHandler::init(const T& param)
{
    // Compiles with no problem
    // return errorHandler(1, &IOPinHandler::ptrFun); 

    // Compiles with no problem
    // auto fix = [](IOPinHandler& obj) -> bool 
    // {
    //    return true; 
    // };
    // return errorHandler(1, static_cast<bool(*)(IOPinHandler&)>(fix));

    // Does not compiles
    auto fix = [&](IOPinHandler& obj) -> bool 
    {
        return true; 
    };
    return errorHandler(1, fix);    
}

int main(void)
{
    IOPinHandler a(1,'c',true);
    return 0;
}

CodePudding user response:

If I have understood correctly, you don't need any member function overload or even std::function.

You can make the Callable as template parameter and let the compiler deduce the type of it for you.

template<typename Callable, typename... Args>
auto errorHandler(const GpioStatusCode& code, Callable callback, Args&&... args)
  // -> decltype(callback(args...))---> if compiler does not support C  14 or later
{
    // if needed within the function body
    using HandlerReturn = decltype(callback(args...)); 
    // ... do something
    return callback(std::forward<Args>(args)...);
}

Live Demo

CodePudding user response:

The issue you're facing is that when you pass a lambda with capture to errorHandler, the type of the lambda is not a function pointer, so the first overload of errorHandler is not a viable match.

The type of a lambda with capture is an unspecified, unique class type, whereas std::function is a type that can hold any callable object, including function pointers and objects of class types, such as the type of the lambda with capture.

You can resolve this by changing the first overload of errorHandler to take
const std::function<HandlerReturn(IOPinHandler&, const Args&...)>&
instead of HandlerReturn(*)(IOPinHandler&, const Args&...).

template<typename HandlerReturn = void, typename ...Args>
HandlerReturn errorHandler(const int& code, const std::function<HandlerReturn(IOPinHandler&, const Args&...)>& callback, const Args&... args);

By taking const std::function<HandlerReturn(IOPinHandler&, const Args&...)>&
instead of HandlerReturn(*)(IOPinHandler&, const Args&...), the first overload of errorHandler can match both function pointers and objects of class types, such as the type of the lambda with capture.

Here is an example using the updated code:

#include <functional>
#include <iostream>

class IOPinHandler
{
    public:
        template<typename HandlerReturn = void, typename ...Args>
        HandlerReturn errorHandler(const int& code, const std::function<HandlerReturn(IOPinHandler&, const Args&...)>& callback, const Args&... args)
        {
            return callback(*this, args...);
        }
};

int main()
{
    IOPinHandler handler;
    auto fix = [&](IOPinHandler& obj) -> bool 
    {
        return true; 
    };
    std::cout << handler.errorHandler(0, fix) << std::endl;
    return 0;
}

This program outputs "1" as expected.

  • Related