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 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) {}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With