template<typename ReturnT, typename... ParamT>
void foo(std::function<ReturnT(ParamT...)> callback)
{}
template<typename ReturnT, typename ParamT>
void bar(std::function<ReturnT(ParamT)> callback)
{}
main()
{
foo<int, int>([](int x){ return x; }); // no instance of function
// template matches argument list
bar<int, int>([](int x){ return x; }); // OK
}
The only difference between foo and bar is that foo has variadic arguments. Somehow the compiler is able to convert the lambda to a std::function in bar.
To my understanding, template type deduction doesn't consider type conversions. So shouldn't both fail?
template<typename ReturnT, typename... ParamT>
void foo(std::function<ReturnT(ParamT...)> callback)
{}
now, foo<int,int>
is foo<ReturnT=int, ParamsT starts with {int}>
.
It does not fully specify ParamT
. In fact, there is no way to fully specify ParamT
.
As an incompletely specified template, deduction occurs, and fails. It doesn't try "what if I just assume the pack doesn't go any further".
You can fix this with:
template<typename ReturnT, typename... ParamT>
void foo(block_deduction<std::function<ReturnT(ParamT...)>> callback)
{}
where block_deduction
looks like:
template<class T>
struct block_deduction_helper { using type=T; }:
template<class T>
using block_deduction = typename block_deduction_helper<T>::type;
now deduction is blocked on foo
's first argument.
And your code works.
Of course, if you pass in a std::function
it will no longer auto-deduce arguments.
Note that deducing the type of a a type erasure type like std::function
is usually code smell.
Replace both with:
template<class F>
void bar(F callback)
{}
if you must get arguments, use function traits helpers (there are many on SO). If you just need return value, there are std
traits that already work that out.
In c++17 you can do this:
tempate<class R, class...Args>
void bar( std::function<R(Args...)> f ) {}
template<class F>
void bar( F f ) {
std::function std_f = std::move(f);
bar(std_f);
}
using the c++17 deduction guides feature.