As far as I can tell, Clang and GCC are right in all four cases according to the standard, even though their behaviour may seem counter-intuitive, especially in cases #2
and #4
.
There are two main steps in the analysis of the function calls in the code sample. The first one is template argument deduction and substitution. When that completes, it yields a declaration of a specialization (of either g
or h
) where all template parameters have been replaced with actual types.
Then, the second step attempts to match f
's overloads against the actual pointer-to-function parameter that was constructed in the previous step. The best match is chosen according to the rules in [13.4] - Address of overloaded function; in our cases this is pretty simple, as there are no templates among the overloads, so we have either one perfect match or none at all.
The key point to understanding what happens here is that an ambiguity in the first step doesn't necessarily mean that the whole process fails.
The quotes below are from N4296 but the content hasn't changed since C++11.
[14.8.2.1p6] describes the process of template argument deduction when a function parameter is a pointer to function (emphasis mine):
When P is a function type, pointer to function type, or pointer to
member function type:
— If the argument is an overload set containing one or more function
templates, the parameter is treated as a non-deduced context.
— If the argument is an overload set (not
containing function templates), trial argument deduction is attempted
using each of the members of the set. If deduction succeeds for only
one of the overload set members, that member is used as the argument
value for the deduction. If deduction succeeds for more than one
member of the overload set the parameter is treated as a non-deduced
context.
For completeness, [14.8.2.5p5] clarifies that the same rule applies even when there's no match:
The non-deduced contexts are: [...]
— A function parameter for which argument deduction cannot be done because
the associated function argument is a function, or a set of overloaded
functions (13.4), and one or more of the following apply:
— more than one function matches
the function parameter type (resulting in an ambiguous deduction), or
— no function matches the function parameter type, or
— the set of functions supplied as an argument contains one or more
function templates.
So, no hard errors because of ambiguity in these cases. Instead, all template parameters are in non-deduced contexts in all our cases. This combines with [14.8.1p3]:
[...] A trailing template parameter pack (14.5.3) not otherwise
deduced will be deduced to an empty sequence of template arguments.
[...]
While the use of the word "deduced" is confusing here, I take this to mean that a template parameter pack is set to the empty sequence if no elements can be deduced for it from any source and there are no template arguments explicitly specified for it.
Now, the error messages from Clang and GCC start to make sense (an error message that only makes sense after you understand why the error occurs is not exactly the definition of a helpful error message, but I guess it's better than nothing):
#1
: Since Ts
is the empty sequence, the parameter of g
's specialization is indeed void (*)()
in this case. The compiler then tries to match one of the overloads to the destination type and fails.
#3
: T
only appears in a non-deduced context and is not explicitly specified (and it's not a parameter pack, so it cannot be "empty"), so a specialization declaration cannot be constructed for h
, hence the message.
For the cases that do compile:
#2
: Ts
cannot be deduced, but one template parameter is explicitly specified for it, so Ts
is int
, making g
's specialization's parameter void (*)(int)
. The overloads are then matched against this destination type, and the first one is chosen.
#4
: T
is explicitly specified as int
and Ts
is the empty sequence, so h
's specialization's parameter is void (*)(int)
, the same as above.
When we eliminate one of the overloads, we eliminate the ambiguity during template argument deduction, so the template parameters are no longer in non-deduced contexts, allowing them to be deduced according to the remaining overload.
A quick verification is that adding a third overload
void f() { }
allows case #1
to compile, which is consistent with all of the above.
I suppose things were specified this way to allow template arguments involved in pointer-to-function parameters to be obtained from other sources, like other function arguments or explicitly-specified template arguments, even when template argument deduction can't be done based on the pointer-to-function parameter itself. This allows a function template specialization declaration to be constructed in more cases. Since the overloads are then matched against the parameter of the synthesized specialization, this means we have a way to select an overload even if template argument deduction is ambiguous. Quite useful if this is what you're after, terribly confusing in some other cases - nothing unusual, really.
The funny thing is that MSVC's error message, while apparently nice and helpful, is actually misleading for #1
, somewhat but not quite helpful for #3
, and incorrect for #4
. Also, its behaviour for #2
is a side effect of a separate problem in its implementation, as explained in the question; if it weren't for that, it would probably issue the same incorrect error message for #2
as well.
This is not to say that I like Clang's and GCC's error messages for #1
and #3
; I think they should at least include a note about the non-deduced context and the reason it occurs.