views:

94

answers:

3

I'm using a library that defines an interface:

template<class desttype>
void connect(desttype* pclass, void (desttype::*pmemfun)());

and I have a small heirarchy

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?

+7  A: 

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.

AndreyT
Jesse Beder
Sometimes you just need someone to put words on concepts and explain what they represent... man do I love SO for this! Very nice explanation.
Matthieu M.
A: 

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 class SomeClass. It is not a member of DerivedClass! (Some compilers behave slightly differently: e.g., for Digital Mars C++, &DerivedClass::some_member_func is undefined in this case.) But, if DerivedClass overrides some_member_func, the code won't compile, because &DerivedClass::some_member_func has now become a member function pointer of class DerivedClass!

Michael Burr
A: 

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() )

Ramon Zarazua
Unfortunately, I can't (without undue effort, at least) change the signature of the method.
Jesse Beder
In that case, the only solution left is to explicitly specify the template argument, as AndreyT said.
Ramon Zarazua