Deduce lambda return and arguments passed to constructor

Viewed 259

I have the following class:

template<typename R, typename... Args>
class Callable final
{
public:
    Callable(void* args, R(*fn)(Args...));
    Callable(void* args, std::function<R(Args...)> &&fn);
    
    /* parse args into a tuple<Args...> and invoke callable */
    void* invoke();
    
private:
    void* args;
    std::function<R(Args...)> callable;
};

and I can use it like:

void* test(void* a, const char* b, const char* c)
{
   return nullptr;
}

Callable(args, test).invoke();
Callable(args, std::function([](void* a, void* b, void* c){})).invoke();

but it doesn't allow me to do:

Callable(args, [](void* a, void* b, void* c){}).invoke();
//No viable constructor or deduction guide for deduction of template arguments of 'Callable'

I must wrap the lambda in an std::function. Is there a way to allow my class to accept a lambda directly and store it as an std::function without having to explicitly specify std::function(lambda) as a constructor parameter?

I don't want to do Callable(args, std::function([](void* a, void* b, void* c){})).invoke(); which explicitly wraps the lambda in a function. I want to pass the lambda directly and let the constructor store it as a function internally.

How can I do that?

1 Answers

If we're allowed to modify the template signature of Callable to remove the pack Args... in favour of a single parameter, we can write our own deduction guide for Callable:

template<typename Fn>
class Callable final {
public:
    Callable(void* args, std::function<Fn>&&);

    void* invoke();
    
private:
    void* args;
    std::function<Fn> callable;
};

template<typename>
struct Get_fn_type;

template<typename R, typename C, typename... Args>
struct Get_fn_type<R(C::*)(Args...) const> {
    using type = R(Args...);
};

template<typename R, typename C, typename... Args>
struct Get_fn_type<R(C::*)(Args...)> {   // for mutable lambdas
    using type = R(Args...);
};

template<class Fn>
Callable(void*, Fn) -> Callable<
    typename Get_fn_type<decltype(&Fn::operator())>::type>;

template<class Fn>
Callable(void*, Fn*) -> Callable<Fn>;

Now we can do:

Callable(args, test).invoke();
Callable(args, std::function([](void* a, void* b, void* c){})).invoke();
Callable(args, [](void* a, void* b, void* c) {}).invoke();
Callable(args, [](void* a, void* b, void* c) mutable {}).invoke();

Demo


One drawback of having one template parameter instead of pack might be the difficulty of defining a tuple of Args... explicitly. A helper type trait could be used to get std::tuple type from Fn:

template<typename>
struct Tuple_from_args;

template<typename R, typename... Args>
struct Tuple_from_args<R(Args...)> {
    using type = std::tuple<Args...>;
};
Related