Thursday, July 5, 2007

Breaking polymorphism with templates

Intriguing title, huh?

So here's the issue. Say I want to have a collection of objects, each of which is an instance of a template class (with various template types). Not going to work, you say, cause each template typed version of the object is a different type, and you can't have a collection of different types, unless they are derived from a base type. Fine, no problem, I can have a non-templated base type as the pointer type for the list; something like this simplified example code. Now the fun starts...

class CBaseClass
{
public:
    virtual ~CBaseClass();
};

template< typename TYPE >
class CSubClass : public CBaseClass
{
public:
    TYPE m_tValue;
};

Say I want to get the value from an element in the list, where the type of the value is the template type of the subclass of the actual object instance. Simple enough conceptually, but wait... there's an issue. Iterating the list give me pointers to the base class, and I need to call a method which is explicitly or implicitly aware of the subclass type. And here we come to the quintessential example for polymorphism: CShape, CSquare, virtual void Draw(), etc.

So I just add a virtual method, specify the type I want to get out as a template parameter, override it in the subclass, return m_tValue, and we're done, right? Something like this, for example:

class CBaseClass
{
public:
    virtual ~CBaseClass();

    template< typename TYPE >
    virtual TYPE GetValue() = 0;
};

bool bHappy = pBaseClass->GetValue< bool >();

Um... see, here's where C++ is kinda broken. You can't have a template virtual method, it's not allowed.

... So how can we get m_tValue, when all we have is a CBaseClass*?

Coding gymnastics, using RTTI, explicit type lists, a whole crap load of ugly, runtime-check-only template code, and very limited extensibility / flexibility. Basically, breaking the whole point of templates and polymorphism. Seriously, the code is simpler if you have one class with a void*, a size, and an enum for the type in it, and you forget about virtual functions, templates, or anything fancy designed to eliminate the need for void*'s, sizes, and explicit runtime type storage.

Somebody in the C++ committee should seriously look at this, figure out a good solution, and fix the standard, cause it's broken.

No comments: