Say you have two structures, Generic_A
and Generic_B
. Generic_B
is derived from Generic_A
. Why is it that when Generic_B
tries to access a method in its parent, Generic_A
, it generates the following error:
test2.cpp: In function 'int main()':
test2.cpp:26: error: no matching function for call to 'case1(void (Generic_A::*)()'
This code, compiled using gcc version 4.4.6, replicates the problem:
#include <stdio.h>
struct Generic_A
{
void p1() { printf("%s\n", __PRETTY_FUNCTION__); };
};
struct Generic_B : public Generic_A
{
void p2() { printf("%s\n", __PRETTY_FUNCTION__); };
};
template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) ) {
printf("%s\n", __PRETTY_FUNCTION__);
}
template <class T>
void case2( void (T::*p)() ) {
printf("%s\n", __PRETTY_FUNCTION__);
}
main()
{
//generates error
case1<Generic_B>(&Generic_B::p1);
//compiles fine
case2<Generic_B>(&Generic_B::p1);
}
The only apparent difference between the two function calls is that case1()
has a template argument parameter, and case2()
doesn't. Shouldn't they both allow you to pass a function pointer to a method in Generic_B's parent (ie &Generic_B::p1
)?
Also, casting the function pointer in case1
seems to sometimes resolve the error:
case1<Generic_B>( (void (Generic_B::*)()) &Generic_B::p1);
What is going on?
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 toA
(after the typeA
is transformed as described above). However, there are three cases that allow a difference:
If the original
P
is a reference type, the deducedA
(i.e., the type referred to by the reference) can be more cv-qualified than the transformedA
.The transformed
A
can be another pointer or pointer to member type that can be converted to the deducedA
via a qualification conversion (4.4).If
P
is a class andP
has the form simple-template-id, then the transformedA
can be a derived class of the deducedA
. Likewise, ifP
is a pointer to a class of the form simple-template-id, the transformedA
can be a pointer to a derived class pointed to by the deducedA
.
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.