2

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?

Evg
  • 25,259
  • 5
  • 41
  • 83
Brandon
  • 22,723
  • 11
  • 93
  • 186
  • @Evg but it works if I just do `std::function(lambda)` without specifying the types it deduces fine.. so I was just wondering if it's possible to get rid of the `std::function` part. The linked question states that you can't create an `std::function` without specifying the types.. but I'm able to do so :S – Brandon Nov 11 '20 at 07:33
  • `template Callable(void* args, Fn fn) : args(args), callable(fn) {}` – n. m. could be an AI Nov 11 '20 at 07:33
  • By the way the class as written can be replaced by `std::function` and initialised with a call to `std::bind` or a lambda. – n. m. could be an AI Nov 11 '20 at 07:37
  • @Brandon, you can do it thanks to [deduction guides](https://en.cppreference.com/w/cpp/utility/functional/function/deduction_guides). – Evg Nov 11 '20 at 07:38
  • Duplicate of https://stackoverflow.com/questions/22298865/automatic-decay-of-lambda-to-function-pointer-when-passing-to-template-function The problem is that the lambda must decay to a function pointer, so you must either cast it with the right signature or use a trick like suggested – sandwood Nov 11 '20 at 08:08
  • @sandwood But what if OP wants to pass a capturing lambda, too? – Evg Nov 11 '20 at 08:14
  • @Evg : with OP code, it's not possible. Lambda function can decay to function pointer only if they do not capture parameters. So none of the two Callable proposed would be a valide candidate. – sandwood Nov 11 '20 at 08:24
  • 1
    @sandwood `std::function` can wrap capturing lambdas. – Evg Nov 11 '20 at 08:26
  • Yes. of course. but none of the 2 OP Callable would transform magically the lamda to std::function as you said. Somehow (see answer below, other tricks also exist), it must be explicitly done – sandwood Nov 11 '20 at 08:29
  • @sandwood OP wants an implicit solution. That's the question. – Evg Nov 11 '20 at 08:31
  • Why are you using `void*`? Storing the callable directly (without using std::function) and passing the arguments as a template parameter pack seems like a much better solution. – super Nov 11 '20 at 08:32
  • Another option, since you are storing the arguments in the struct, is to simply pass in a lambda that takes no arguments and let the lambda capture the arguments and forward the call. This whole design smells. – super Nov 11 '20 at 08:39
  • @super; This is what I have to deal with when calling C++ code from Python OR write it all out manually.. I am given a `PyObject*` (or `void*`) for arguments, then from that I parse it into a `tuple` with `PyArg_ParseTuple(void* args, const char* format, &arg1, &arg2, &arg3, ...);` for every function.. In order to do all of this automatically, I decided to wrap any C++ callable in a `std::function` that can be called by Python code: https://pastebin.com/EsqGBmYY just like `PyBind11` does. I just wanted to understand how it works. My code works thanks to the answer now :) – Brandon Nov 11 '20 at 14:14

1 Answers1

3

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...>;
};
Evg
  • 25,259
  • 5
  • 41
  • 83
  • Could you explain the `C::*` notation pleas? Is this a pointer to a type `C`? What do you need the scope resolution for? – User12547645 Nov 11 '20 at 08:46
  • @User12547645, `C::*` is [a pointer to a member function](https://isocpp.org/wiki/faq/pointers-to-members) of class `C`. Lambda is just a syntactic sugar. Internally, it's a class (with some unique name) with a member function `operator()`. So, `C` will be deduced to that unique type, which is of no interest to us. – Evg Nov 11 '20 at 08:50
  • So since the lambda only has one member function the compiler will know that `C::*` refers to that function? Got it :) – User12547645 Nov 11 '20 at 08:53
  • @User12547645 Not exactly. We pass a pointer to the function explicitly: `&Fn::operator()`. What is important is that we have only one `operator()` so that no overload resolution is needed to pick a particular function. – Evg Nov 11 '20 at 08:55
  • 1
    I changed my template class to `Callable` and your code above, and it worked. That way I got to keep the `Args...` to construct the tuple. But I will try out your tuple helper anyway and see :D – Brandon Nov 11 '20 at 14:05