void (*p)(int) = func;
does overload resolution similarly to func(1)
, with one crucial difference.
Both the call and the assignment will first lookup func
and find the two function templates. Template argument deduction will be done on both templates Tp = int
will be deduced for both of them.
Then Tp
will be substituted into the templates. This is why your static_assert(depend_type<Tp>::false_);
will fire because the compiler needs to figure out what typename cont<Tp>::x
is in the return type of the second function.
Then the best viable function needs to be found. For overload resolution in the call (func(1)
), neither overload is better than the other (both take an argument int
that came from Tp
). There is nothing else to compare these two overloads, so it is ambiguous.
But in the assignment case (void (*p)(int) = func;
), the return type is also considered. In the first function template, void
didn't come from a template, so it is better than the second function template, where the void
is a dependent type. So the first function template is chosen.
This might be easier to understand with a different example:
template<class T>
T** f() {
std::cout << "T**\n";
return nullptr;
}
template<class T>
T* f() {
std::cout << "T*\n";
return nullptr;
}
template<class T>
T f() {
std::cout << "T\n";
return T{};
}
int main() {
// void** p = f<void>(); // Doesn't compile: return type doesn't participate in ranking most viable function
// void** is a better match for T** than T* or T (it's more specialized)
static_cast<void**(&)()>(f)(); // T**
// void* is a better match for T* than T
static_cast<void*(&)()>(f)(); // T*
// T = void for the third function template is the only option
static_cast<void(&)()>(f)(); // T
}
This is one of three scenarios where function return type is considered during overload resolution. The second being type conversion operator return types and the third during explicit (full) specialization.
func(1)
is expected to fail, right? – Liguriancont<Tp>::x
doesn't participate in the function type deducing invoid (*p)(int) = func;
, is non-deduced context as always when it can't be done in 1 stage. See What is a non-deduced context? – ParrishTp
is deduced fromfunc(Tp)
and thenTp
is substituted intypename cont<Tp>::x
– Ligurianfunc(1)
says nothing about the return type you expect from your called function (only to be thrown away, but still); your function-pointer assignment explicitly requires a function with that specific return type, and there's a template for that specific return type. – Abecedary