|
Template Overloading For Base Class Pointers
Submitted by |
Let's say you had a template function which took as a parameter a pointer to a
type. Now let's say you wanted to specialize a template function for a specific
type of pointer. You'd do it like such:
// Forward declaration
class B;
// Generic template function
template<typename T bool MyFunc(T *t)
{ return false;
}
// Specialized template function for B
template< bool MyFunc(B *t)
{ return true;
} |
This will work great if you call MyFunc with an actual pointer to a B object,
but what happens when you have a class derived from B (class D) and call MyFunc
with its pointer?
// derived class:
class D : public B
{ // definition
};
void CallingCode( D *d )
{
assert( MyFunc(d) );
} |
The code WILL throw the assertion because the template compiler resolves the
function call to our generic template function version instead of the
specialized version. It looks to me that the compiler looks for specializations
of the exact type and not types that can be converted to. I don't know if this
behaviour complies to the C++ standard or not, but I for one think that it would
be useful if you could use polymorphism with templates.
So now we know the problem, here's the solution.
The use of templates implies that we'd like to be able to overload a function
for any given type. In C++ we can do this without templates by using ellipsis.
bool IsOfTypeFunc(...)
{ return false;
} |
This function will accept all arguments of any type and even of any number of
parameters. Of course, use of the ellipsis is inherently type unsafe and just
plain dangerous, but we'll just ignore this for now. We can now overload
IsOfTypeFunc with a version that takes a pointer to B:
bool IsOfTypeFunc(B *t)
{ return true;
} |
Unlike templates, calling IsOfTypeFunc with a derived B class pointer will
resolve to our base class overloaded version. This adheres to the rules of
normal function overloading except that the function with the ellipsis acts as a
"catch all" for any unresolvable type. So now we can write our calling code as:
void CallingCode( D *d )
{
if ( IsOfTypeFunc(d) )
MyFunc( (B*)d );
else
MyFunc( d );
} |
Well now, that if else statement is pretty ugly, but at least it should work now
right? Or will it?
The code will execute correctly and the second function will indeed never be
called during runtime, however the problem here is that the compiler will still
replace the second MyFunc call with the generic template code. As a result, if
we created the specialization to overcome a compiling error when typename T is
replaced with B, we'll still be generating that errorous code. For example:
// Only expecting built-in types for this func (stupid design but this is just for illustrative purposes)
template< typename T bool MyFunc( T *t )
{
char achar = static_cast<char(*t); //Compiler error if T cannot be converted to char
return achar;
} |
As a result, the calling code will never compile.
So what we'd like to do is have to only call the function we want, period.
Well, let's go back to using templates!
// Revised generic template function
template< typename T bool SolutionFunc( T *t, ... )
{
WeCanNowPutGarbageHereSinceThisCodeWillNeverBeInstantiatedFromOurCallingCode;
return false;
}
// Pseudo-specialized version for pointers to objects of type B
template< typename T bool SolutionFunc( T *t, B *b)
{ return true;
}
// Helper function to foward on request
template< typename T bool ForwardingFunc( T * t )
{ return SolutionFunc( t, t );
}
void CallingCode(D *d)
{
assert( ForwardingFunc(d) ); // THIS WILL WORK!
} |
Not only do we generate the correct function call, it is the only function call
that is generated. We've also gotten rid of that ugly if else statement, plus
we now have access to the real object and it's type in the ellipsis function in
a compiler enforced, type safe way. And of course, the purpose of this little
exercise - using templates with base class pointers - also works!
It doesn't get much better than that.
Notes: This code was tested successfully with VC++ 6.0. Props to Gerard Lynch
for discovering this stuff in the first place.
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|