That looks like a compiler bug; the compiler tries template argument deduction when all the arguments are already explicitly specified and hence no deduction is necessary.
Or maybe the bug is with substitution, which should succeed.
According to the standard, it is possible to explicitly specify variadic pack arguments. See an example in [temp.arg.explicit]/5:
template<class ... Args> void f2();
void g() {
f2<char, short, int, long>(); // OK
}
When all template arguments are known, the compiler is supposed to simply instantiate the template and be done with it; overload resolution then proceeds normally.
To work around the issue we can disable template argument deduction by introducing a non-deduced context. For example like this:
template<typename T> using no_deduce = typename std::common_type<T>::type;
template<typename A, typename B, typename ...Fs>
void func_tmpl1(no_deduce<std::function<A(Fs..., B)>> callable)
{
}
template<typename A, typename ...Fs>
void func_tmpl2(no_deduce<std::function<A(Fs...)>> callable)
{
}
(The ::type
here is a dependent type and becomes a non-deduced context)
Now it compiles fine in g++
and clang++
. link to coliru
Having said that, note that std::function
is primarily meant for type erasure and is a costly abstraction as it incurs an extra indirection at run time and is a heavy object to pass around since it tries to store a copy of any possible functor while avoiding heap allocation (which often still takes place - then it's a large empty object plus a heap allocation).
Since your functions are already templates, you don't really need type erasure; it is easier and more efficient to just take callable
as a template argument.
template<typename Func>
void func_tmpl(Func callable) // that's all
{
}
Or, if you have to differentiate by callable
arguments, can use some SFINAE:
#include <functional>
class Cls1{};
template<typename A, typename B, typename ...Fs, typename Func,
typename = std::enable_if_t<std::is_invocable_r_v<A, Func, Fs..., B> > >
void func_tmpl1(Func callable)
{
}
template<typename A, typename B, typename ...Fs, typename Func,
typename = std::enable_if_t<std::is_invocable_r_v<A, Func, B, Fs...> > >
void func_tmpl2(Func callable)
{
}
void func0(std::function<void(float, Cls1)> callable)
{
}
int main()
{
std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
func0(f1); // func0 is not a template - so it requires type erasure
func0([](float a, Cls1 b){});
func_tmpl1<void, Cls1, float>(f1); // #1 OK
func_tmpl2<void, float, Cls1>(f1); // #2 OK
func_tmpl1<void, Cls1, float>([](float a, Cls1 b) {}); // #3 OK
func_tmpl2<void, float, Cls1>([](float a, Cls1 b) {}); // #4 OK
return 0;
}
link to coliru