Search code examples
c++templatespolymorphismvirtualfunction-pointers

Polymorphism without virtual in C++ for multi level inheritance


I have a situation where I need to achieve polymorphism without vtable. Here is what I am trying to do

  • There is a class hierarchy: C extends B, B extends A
  • The idea is to declare a function pointer in A and constructors of B and C assign their corresponding methods to the function pointer in A
  • With the code below I am able to achieve polymorphism for class C but not for class B.

Obviously I am missing something here. I am not sure if this is even possible. Greatly appreciate any insights into this problem.

I can do this with the below code

A<C> *c = new C();
c->BasePrint(); //Reached C's Print

but not this

// A<B> *b = new B();
// b->BasePrint(); //Intentionally incorrect to demonstrate the problem.

Is there any way to achieve this?

template <typename T>
class A
{
public:
    typedef void (T::*PrintFn)(void);
protected:
    PrintFn printFn;
public:
    void BasePrint()
    {
        if(printFn)
            (((T*)this)->*printFn)();
    }
};


template <typename T>
class B : public A<T>
{
public:
    B()
    {
        printFn = &B::Print;
    }

    void Print()
    {
        //Print B
    }
};



class C : public B<C>
{
public:
    C()
    {
        printFn = &C::Print;
    }

    void Print()
    {
        //Print C
    }
};

Solution

  • #include <iostream>
    #include <typeinfo>
    
    struct own_type {};
    
    template<template<typename T>class CRTP, typename In, typename D>
    struct DoCRTP: CRTP<In> {};
    template<template<typename T>class CRTP, typename D>
    struct DoCRTP<CRTP, own_type, D>: CRTP<D> {};
    
    template<typename D>
    struct A {
       D* self() { return static_cast<D*>(this); }
       D const* self() const { return static_cast<D*>(this); }
       A() {
          std::cout << "A<" << typeid(D).name() << ">\n";
          self()->print();
       }
    };
    
    template<typename T=own_type>
    struct B:DoCRTP<A, T, B<T>> {
       B() {
          std::cout << "B<" << typeid(T).name() << ">\n";
       }
       void print() { std::cout<<"I am a B\n"; }
    };
    
    template<typename T=own_type>
    struct C:DoCRTP<B, T, C<T>> {
       C() {
          std::cout << "C<" << typeid(T).name() << ">\n";
       }
       void print() { std::cout<<"I am a C\n"; }
    };
    
    void test() {
       std::cout << "Instance of B<>:\n";
       B<> b;
       std::cout << "Instance of C<>:\n";
       C<> c;
    }
    
    int main() {
       test();
    }
    

    Here we have a way you can pass in the most derived class, and if you pass in nothing you are assumed to be the most derived class.

    However, there is a problem with your design -- A already fully knows its type situation, so there is no need for virtual behavior! BasePrint could static_cast<T*>(this)->Print() and you'd do away with your overhead.

    The fundamental problem you have is that you are storing specific-type member function pointers in your base class A.

    A template-less A could store pointers to non-specific type function pointers -- say "static" ones that explicitly take an A* as the first argument. In C++11, you could auto-build these functions from member functions. In C++03, std::bind should let you convert your member function pointers to D to functions that take an A* as the first argument.