There are several questions on SO that relate to casting lambdas to std::function
s, but I have yet to see one that uses a parameter pack for the argument list.
Here is a solution that will let you call functor
without specifying it's template argument:
#include <functional>
#include <type_traits>
template <class T> struct Fun_trait {};
template <class T, class... Args, class Ret>
struct Fun_trait<auto (T::*) (Args...) const -> Ret>
{
using F = std::function<auto (Args...) -> Ret>;
};
template <class TReturn, class... TArgs>
void functor(std::function<TReturn (TArgs...)> f) {}
template <class F>
std::void_t<decltype(&F::operator())>
functor(F f)
{
return functor<typename Fun_trait<decltype(&F::operator())>::F>(f);
};
int main(int argc, char * argv[])
{
auto x = [] (int a, int b) { return a * b; };
// nice and easy:
functor(x);
return 0;
}
This is just a lazy first draft to get you started. You need to expand it to support forwarding and non-const operator()
.
It works in 2 stages:
1st we have Fun_trait
who - for pointer method types (e.g. the operator()
of a lambda) - has defined an alias F
for the required std::function
argument type.
Next we have a overload of your function functor
which via SFINAE with std::void_t
kicks in only for functors with a non-overloaded operator()
(e.g. a lambda) and then using the above trait calls the main function functor
with the correct template argument deduced.
The issue is that the compiler doesn't know that you've intended int, int
to be the whole of TArgs
, and so tries to deduce the remainder of TArgs
from the argument f
.
For example, this would be valid:
Functor<int, int, int>(std::function<int(int, int, char, float)>{});
// TArgs := {int, int, [...] char, float}
So you need to instruct the compiler to not try to deduce the remainder of TArgs
. For example, you could write:
(*Functor<int, int, int>)(x);
Or you could write Functor
with a non-decomposed signature Sig
:
template <Sig>
void Functor(std::function<Sig> f) {}
Or you could wrap the use of TArgs
in the parameter f
in a non-deduced context:
template <typename TReturn, typename ... TArgs>
void Functor(std::function<std::conditional_t<false, void, TReturn (TArgs...)>> f) {}
It is very rarely a good idea to cast a lambda to a std::function
in a template
if you are just going to call it. std::function
is type-erasure, and templated type erasure only makes sense if you are going to "pass it on" somewhere else and/or return it.
In any case, try this:
template <class Sig>
void Functor(std::function<Sig> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor<int(int, int)>(x);
return 0;
}
but you should really just do
template <class F>
void Functor(F f) {}
which is perfectly type-safe.
If you want early type checking, you could write
template<class Sig, class F>
struct signature_compatible;
template<class R, class...Args, class F>
struct signature_compatible<R(Args...), F> :
std::is_consructible< R, std::result_of_t<F(Args...)>>
{};
then do
template <class Sig, class F>
void Functor(F f) {
static_assert( signature_compatible<Sig, F&>::value, "bad signature" );
}
but only if you really need to.
This fails:
#include <functional>
template <typename TReturn, typename ... TArgs>
void Functor(std::function<TReturn (TArgs...)> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor<int, int, int>(x);
return 0;
}
because you're not specifying that the entirety of TArgs...
is {int, int}
. What you are doing is specifying that the first two types are {int, int}
. Effectively, by providing those three types, we've turned the deduction problem into:
template <typename ... TArgs>
void Functor(std::function<int(int, int, TArgs...)> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor(x);
return 0;
}
This doesn't compile because a lambda isn't a std::function
(or derived from one), which is the same reason you couldn't have called this without having provided any types to begin with.
The non-variadic version doesn't have this problem, since you've provided all the types.
But really, what you want is:
template <typename F>
void Functor(F ) {}
This doesn't lose you any type safety. It's using std::function
that loses type information, since that class template exists to type erase.