Member function pointers in a hierarchy
-
25-09-2019 - |
Question
I'm using a library that defines an interface:
template<class desttype>
void connect(desttype* pclass, void (desttype::*pmemfun)());
and I have a small hierarchy
class base {
void foo();
};
class derived: public base { ... };
In a member function of derived
, I want to call
connect(this, &derived::foo);
but it seems that &derived::foo
is actually a member function pointer of base
; gcc spits out
error: no matching function for call to ‘connect(derived* const&, void (base::* const&)())’
I can get around this by explicitly casting this
to base *
; but why can't the compiler match the call with desttype = base
(since derived *
can be implicitly cast to base *
)?
Also, why is &derived::foo
not a member function pointer of derived
?
Solution
Firstly, when you do &class::member
the type of the result is always based on the class that member actually declared in. That's just how unary &
works in C++.
Secondly, the code does not compile because the template argument deduction fails. From the first argument it derives that desttype = derived
, while from the second one it derives that desttype = base
. This is what makes the compilation to fail. The template argument deduction rules in C++ don't consider the fact that this
can be converted to base *
type. Moreover, one can argue that instead of converting this
to base *
type, the proper way would be to convert &derived::foo
from pointer-to-base-member to pointer-to-derived-member type. Both approaches are equally viable (see below).
Thirdly, member pointers in C++ obey the rules of contra-variance, which means that a pointer to a base class member can be implicitly converted to a pointer to a derived class member. In your case, all you need to do is to help the compiler get through template argument deduction by specifying the argument explicitly, and the code should compile
connect<derived>(this, &derived::foo);
The above should compile because of contra-variance of &derived::foo
pointer, even though it is a pointer to base
member. Alternatively you can do
connect<base>(this, &derived::foo);
This should also compile because of covariance of this
pointer.
You can also use explicit casts on the actual arguments (as you mention in the question) to get through the deduction ambiguity, but in my opinion in this case the explicitly specified template argument looks better.
OTHER TIPS
Member function pointers have a lot of idiosyncrasies in C++, and various compilers have inconsistencies in how they work. Doug Clugston's article, "Member Function Pointers and the Fastest Possible C++ Delegates", is a very nice overview of how they work (and don't work):
when dealing with derived classes, there are some surprises. For example, the code below will compile on MSVC if you leave the comments intact:
class SomeClass { public: virtual void some_member_func(int x, char *p) { printf("In SomeClass"); }; }; class DerivedClass : public SomeClass { public: // If you uncomment the next line, the code at line (*) will fail! // virtual void some_member_func(int x, char *p) { printf("In DerivedClass"); }; }; int main() { // Declare a member function pointer for SomeClass typedef void (SomeClass::*SomeClassMFP)(int, char*); SomeClassMFP my_memfunc_ptr; my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*) }
Curiously enough,
&DerivedClass::some_member_func
is a member function pointer of classSomeClass
. It is not a member ofDerivedClass
! (Some compilers behave slightly differently: e.g., for Digital Mars C++,&DerivedClass::some_member_func
is undefined in this case.) But, ifDerivedClass
overridessome_member_func
, the code won't compile, because&DerivedClass::some_member_func
has now become a member function pointer of classDerivedClass
!
This is a problem of template argument deduction, if the template arguments are not explicitly stated on the invocation site, then the compiler will not try to do automatic conversion.
The best way to get around this, in my experience, is to declare two template arguments for the function:
template<typename Y, typename T>
void connect(Y * pclass, void (T::*pmemfun)());
In this case, the compiler can happily automatically instantiate for you
void connect<derived, base>(derived * pclass, void (base::*pmemfun)());
This solution is also perfectly safe, since the conversion from derived * to base * will be done inside connect (where I assume you are calling pclass->*pmemfun() )