tags:

views:

96

answers:

2

Suppose that we have the following base and derived classes:

#include <string>
#include <iostream>

class Car {
public:
    void Drive() { std::cout << "Baby, can I drive your car?" << std::endl; }
};

class Porsche : public Car {
};

..and also the following template function:

template <typename T, typename V>
void Function(void (T::*m1)(void), void (V::*m2)(void)) {
    std::cout << (m1 == m2) << std::endl;
}

Why does this compile using GCC:

int main(int argc, char** argv) {
    void (Porsche::*ptr)(void) = &Porsche::Drive;
    Function(ptr, ptr);
    return 0;
}

...but not this?

int main(int argc, char** argv) {
    void (Porsche::*ptr)(void) = &Porsche::Drive;
    Function(&Porsche::Drive, ptr);
    return 0;
}
+6  A: 
int main(int argc, char** argv) {
    void (Porsche::*ptr)(void) = &Porsche::Drive;
    Function(&Porsche::Drive, ptr);
    return 0;
}

ptr has type void (Porsche::*)(), but &Porsche::Drive has type void (Car::*)() (because the member is found in Car, not in Porsche). Thus the function called compares these two member pointers with those types, and the standard says

In addition, pointers to members can be compared, or a pointer to member and a null pointer constant. Pointer to member conversions (4.11) and qualification conversions (4.4) are performed to bring them to a common type. If one operand is a null pointer constant, the common type is the type of the other operand. Otherwise, the common type is a pointer to member type similar (4.4) to the type of one of the operands, with a cv-qualification signature (4.4) that is the union of the cv-qualification signatures of the operand types.

4.11 describes an implicit Standard conversion from void (Base::*)() to void (Derived::*)(). Thus, the comparison would find the common type void (Porsche::*)(). For an object of type Porsche, both member pointers would refer to the same function (which is Car::Drive) - so the comparison would yield true. The comeau web compiler follows this interpretation and compiles your code.

Johannes Schaub - litb
Matt Fichman
A derived class is guaranteed to contain the members of its base classes. That's why `T Base::*` converts implicitly to `T Derived::*` (it's reverse to the usual conversion of `Derived*` to `Base*`).
Johannes Schaub - litb
According to 4.11 the following declaration should work also? `template <typename T> void Function(void (T::*m1)(void), void (T::*m2)(void))`
Kirill V. Lyadvinsky
No that won't work with that call in main, because `T` will be deduced to both `Porsche` and `Car`, which is a contradiction.
Johannes Schaub - litb
Matt Fichman
Its type itself is `void(Car::*)()` - it doesn't need to be converted first.
Johannes Schaub - litb
@litb, I just checked myself. +1 to your answer, it is exactly what I thought. I cannot write so quickly in English yet :)
Kirill V. Lyadvinsky
@Kirill thanks dude :)
Johannes Schaub - litb
+1  A: 

With g++ 4.0.1 the examples you provide compile fine, but it has problems dealing with comparisons of member function pointers of different types (even if the compared element is a virtual method.

struct base
{
   void f() {}
   virtual void g() {}
};
struct derived : public base
{
   void f() {}
   virtual void g() {}
};
template <typename T, typename U>
bool cmp( void (T::*lhs)(), void (U::*rhs)() ) {
   return lhs == rhs;
}
int main()
{
   void (base::*bp)() = &base::f;
   void (base::*bvp)() = &base::g;

   cmp( bp, &base::f );  // compiles
   cmp( bvp, &base::g ); // compiles

   void (derived::*dp)() = &derived::f;
   void (derived::*dvp)() = &derived::g;

   cmp( dp, &derived::f );  // compiles
   cmp( dvp, &derived::g ); // compiles

   cmp( bp, dp );   // fails to compile
   cmp( bvp, dvp ); // fails to compile
}

Now, I have tested the same with comeau online compiler and the whole code compiles fine. I cannot tell you right away what is the standard behavior. I will have to browse the standard for a while.

EDIT: I guess not, litb already did.

David Rodríguez - dribeas