Search code examples
c++inheritancepolymorphismsubclasspointer-to-member

Strange inheritance behavior with base class


I hope someone can help me since can't quite understand how it is possible that the following code can work.

I have a base class with some classes that derive from it. Each derived class has its own non-virtual function not known by the others.

I use a vector to store objects and member functions pointers of derived classes (casting them to member functions of base class, a trick to have a single vector) and call them whenever I need.

Question, how is it possible that in the callback number 3, I can call the function of derived class B using an object of type A?

In the callback number 4, I tried something even more extreme, but maybe I just got lucky with the compiler...

#include <iostream>
#include <vector>

using namespace std;

class Base {
public:
    Base() {}
};

class DerivedA : public Base {
public:
    DerivedA() {}

    void CallbackA() { cout << "Callback A" << endl; }
};

class DerivedB : public Base {
public:
    DerivedB(){}

    void CallbackB() { cout << "Callback B" << endl; }
};

class AnotherBase {
public:
    AnotherBase() {}
};

class DerivedC : public AnotherBase {
public:
    DerivedC() {}

    void CallbackC() { cout << "Callback C" << endl; }
};

typedef  void (Base::* BaseMemFn)();

struct Callback {
    Callback( Base * base, BaseMemFn memFn ) : object(base), function(memFn) {}

    Base* object;
    BaseMemFn function;
};

int main() {
    DerivedA derived_a;
    DerivedB derived_b;
    DerivedC derived_c;

    vector<Callback> callbacks;

    callbacks.push_back(Callback(&derived_a, static_cast<void (Base::*)()>(&DerivedA::CallbackA) ) ); // Callback 1: Ok
    callbacks.push_back(Callback(&derived_b, static_cast<void (Base::*)()>( &DerivedB::CallbackB ) ) ); // Callback 2: Ok
    callbacks.push_back(Callback(&derived_a, static_cast<void (Base::*)()>(&DerivedB::CallbackB))); // Callback 3: Derived A can call a function of derived B ? 
    callbacks.push_back(Callback(reinterpret_cast<Base *>(& derived_c), reinterpret_cast<void (Base::*)()>(&DerivedC::CallbackC))); // Callback 4: Luck ?

    for (Callback& callback : callbacks) {
        (callback.object->*callback.function)();
    }
}

The output:

Callback A
Callback B
Callback B
Callback C

Thank you


Solution

  • I believe Cases 1 and 2 are well-defined:

    [expr.static.cast]/12 A prvalue of type "pointer to member of D of type cv1 T" can be converted to a prvalue of type "pointer to member of B of type cv2 T", where D is a complete class type and B is a base class of D... If class B ... is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the behavior is undefined. [Note: Although class B need not contain the original member, the dynamic type of the object with which indirection through the pointer to member is performed must contain the original member; see [expr.mptr.oper]. —end note]

    [expr.mptr.oper]/4 Abbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined.

    In other words, it's OK to cast from void (D::*)() to void (B::*)(). Moreover, it's OK to call the result of that cast on B* pointer, as long as the actual most-derived object behind that pointer is D or is derived from D (and thus inherits the original D's member).


    Case 3, where you take an adress of DerivedB's member and call it on DerivedA instance, exhibits undefined behavior by violating [expr.mptr.oper]/4 above - calling a member through a pointer whose dynamic type (here, DerivedA) doesn't contain the member (here, DerivedB::CallbackB).


    Case 4, the one with reinterpret_cast<Base*>(&derived_c), exhibits undefined behavior in this cast already (or to be precise, the cast itself is valid, but any attempt to use the resulting Base* pointer is not).