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. This seems broken on my version of g++ (7.1.1-4), and possibly it's just not supported. So is this legal c++17 (by the standard)? If not, why?
#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;
}
The code above won't compile because it fails type deduction. Obviously explicitly typing x
as std::function<int (int, int)>
instead of using auto
makes the error go away. But that doesn't allow me to pass an r-value to Functor
as I would like. I would also like to not loose any type-safety by using another template parameter for the function type.
What I really don't understand is why the above code fails to compile, but the below code is fine and works:
#include <functional>
template <typename TReturn, typename TArgA, typename TArgB>
void Functor(std::function<TReturn (TArgA, TArgB)> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor<int, int, int> (x);
return 0;
}
The issue is that the compiler doesn't know that you've intended
int, int
to be the whole ofTArgs
, and so tries to deduce the remainder ofTArgs
from the argumentf
.For example, this would be valid:
So you need to instruct the compiler to not try to deduce the remainder of
TArgs
. For example, you could write:Or you could write
Functor
with a non-decomposed signatureSig
:Or you could wrap the use of
TArgs
in the parameterf
in a non-deduced context:It is very rarely a good idea to cast a lambda to a
std::function
in atemplate
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:
but you should really just do
which is perfectly type-safe.
If you want early type checking, you could write
then do
but only if you really need to.
Here is a solution that will let you call
functor
without specifying it's template argument: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. theoperator()
of a lambda) - has defined an aliasF
for the requiredstd::function
argument type.Next we have a overload of your function
functor
which via SFINAE withstd::void_t
kicks in only for functors with a non-overloadedoperator()
(e.g. a lambda) and then using the above trait calls the main functionfunctor
with the correct template argument deduced.This fails:
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: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:
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.