This is tricky, but it turns out g++ is correct.
First, the type of expression &Generic_B::p1
is void (Generic_A::*)()
. The compiler uses Generic_B::
to qualify its name lookup and finds the member of Generic_A
. The expression type depends on the definition of the member found, not the type used within the qualified-id.
But it's also legal to have
void (Generic_B::*member)() = &Generic_B::p1;
since there is an implicit conversion from void (Generic_A::*)()
to void (Generic_B::*)()
.
Whenever a function template is used as a function call, the compiler goes through three basic steps (or attempts to):
Substitute any explicit template arguments for the template parameters in the function declaration.
For each function parameter that still involves at least one template parameter, compare the corresponding function argument to that function parameter, to (possibly) deduce those template parameters.
Substitute the deduced template parameters into the function declaration.
In this case, we have the function template declaration
template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) );
where the template parameters are T
and ARGS
, and the function call expression
case1<Generic_B>(&Generic_B::p1)
where the explicit template argument is Generic_B
and the function argument is &Generic_B::p1
.
So step 1, substitute the explicit template argument:
void case1( void (Generic_B::*p)(ARGS...) );
Step 2, compare parameter types and argument types:
The parameter type (P
in Standard section 14.8.2) is void (Generic_B::*)(ARGS...)
. The argument type (A
) is void (Generic_A::*)()
.
C++ Standard (N3485) 14.8.2.1p4:
In general, the deduction process attempts to find template argument values that will make the deduced A
identical to A
(after the type A
is transformed as described above). However, there are three cases that allow a difference:
If the original P
is a reference type, the deduced A
(i.e., the type referred to by the reference) can be more cv-qualified than the transformed A
.
The transformed A
can be another pointer or pointer to member type that can be converted to the deduced A
via a qualification conversion (4.4).
If P
is a class and P
has the form simple-template-id, then the transformed A
can be a derived class of the deduced A
. Likewise, if P
is a pointer to a class of the form simple-template-id, the transformed A
can be a pointer to a derived class pointed to by the deduced A
.
So type deduction allows for certain implicit conversions involving const
/ volatile
and/or derived-to-base conversions, but implicit conversions of pointers to members are not considered.
In the case1
example, type deduction fails, and the function is not a match.
Unfortunately, there's no way to explicitly specify that your template parameter pack ARGS
should be substituted with an empty list. As you already discovered, you can get this working by explicitly doing the necessary pointer to member function conversion yourself, even though it's otherwise valid as an implicit conversion.