2

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
    }
};
Veger
  • 37,240
  • 11
  • 105
  • 116
anumalla
  • 53
  • 5
  • 2
    Could you maybe show some failing test case? – Andy Prowl Mar 22 '13 at 16:56
  • 5
    This is not polymorphism without vtable. This is polymorphism using hand coded vtable as opposed to compiler generated vtable. I can't see any reason for this. – user93353 Mar 22 '13 at 16:58
  • Yes, I agree that it is kind of hand coded vtable. I really have a situation where I need to port a huge code base to a compiler without support for virtual functions. Trying to find a smart way to do this. – anumalla Mar 22 '13 at 17:02
  • 1
    @anumalla: What kind of C++ compiler doesn't support virtual functions? – Oliver Charlesworth Mar 22 '13 at 17:05
  • I think even Tubro C++ which is more than 20 years old supports virtual functions. I do not remember any C++ compiler ever released which didn't support virtual functions. Even the 80s Cfront which was a preprocessor for C supported virtual functions. – user93353 Mar 22 '13 at 17:12

3 Answers3

1

You are not specifying the template parameter for B in:

A<B> *b = new B();

as opposed to its declaration:

template <typename T>
class B : public A<T>

You should go with something long the lines of:

A<B<X>> *b = new B<X>();

with X being a non templated type.

Shoe
  • 74,840
  • 36
  • 166
  • 272
1

I can do this with the below code [...] but not this:

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

Well, the problem here is that B is a class template, and you are not instantiating it. It doesn't have much to do with polymorphism nor with vtables. A class template is just a blueprint (well, a template in fact) for instantiating types by passing arguments to them, but it is not a type per se.

You should use some template arguments when instantiating B. For instance:

A<C>* b = new B<C>();
b->BasePrint();

And you should see this invoking B::Print(). Here is a live example.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
1
#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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks, This is something I was looking for. But I am still unable to do something like with this solution. A<>* a = new A<>(); A<>* b = new B<>(); A<>* c = new C<>(); – anumalla Mar 23 '13 at 06:02
  • Yep. After the code I included a discussion about how to go about that. The idea is that there is both `A` and `A_impl`, where `A` stores your manual vtable of functions like `void (*)(A*,...)`, and `A_impl` takes methods like `void (T::*)(...)` and stores them in `A`. How to do this depends on what other features your compiler supports -- with lambda support this is easy. – Yakk - Adam Nevraumont Mar 23 '13 at 07:35