Reputation: 53
I have a situation where I need to achieve polymorphism without vtable. Here is what I am trying to do
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
}
};
Upvotes: 2
Views: 780
Reputation: 275395
#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.
Upvotes: 1
Reputation: 126432
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.
Upvotes: 1
Reputation: 76240
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.
Upvotes: 1