1)There's an easy solution to the fall-back problem:
BigCircle could also be added to the typelist so that the slot for BigCircle be filled in the vtable:
Visits( *this, Seq< Shape, Circle, BigCircle >::Type(), RenderInvoker() );
The invokers would follow C++ conversion rules and choose render(Circle) for BigCircle as well.
This implies that the visitor class knows about BigCircle.
In general to achieve automatic fall back for a hierarchy:
typedef Seq<all classes that visitor can know of>::Type ClassesInHierarchy;
use the above typedef for all visitors:
Visits( *this, ClassesInHierarchy(), Invoker() );
Since the slot for BigCircle is also filled, there's no additional performance cost for fallback.
2)There's another more flexible approach:
The visitor asks the object being visited -
"Here's how my vtable looks like. Tell me what function in the table i should use?"
Along with the vtable, the visitor builds a status table (std::vector<bool>) that indicates whether a slot in the vtable is filled or not.
Each Visitable class needs to have it's base class typedef-ed:
class BigCircle: public Circle {
....
typedef Circle Super;
};
Rather than (or in addition to) the GetTag function in every visitable object, there's a GetMethodInvocationInfo:
struct InvocationInfo {
size_t vtableIndex;
void* visitedObject;
};
class BigCircle : public Circle {
.....
/// the following can be put in a macro
virtual InvocationInfo GetMethodInvocationInfo(const std::vector<bool>& statusTable)
{
size_t myTag = GetTag(this);
// check whether the visitor has a visit method for this class
if( myTag < statusTable.size() && statusTable[myTag] ) {
return InvocationInfo( myTag, this );
}
// there's no visit method for this class
// fallback to base class
return Super::GetMethodInvocationInfo( statusTable );
}
};
The visitor is given the entire information for making the call - the index to the function and the properly casted object.
in Visitor:
ReturnType operator() ( Base& b )
{
InvocationInfo info= b.GetMethodInvocationInfo( statusTable );
Func thunk = (*vtable_ )[ info.vtableIndex ];
return ( this->*thunk ) ( info.visitedObject );
}
This scheme doesn't require the visitor to know about BigCircle - BigCircle could be any class that inherits from Circle and provides the appropriate GetMethodInvocationInfo implementation.
The look up time is proportional to how many level's of inheritance away BigCircle is from Circle. I think this would be better than falling back through acyclic visitor.
I think both techniques could be used simultaneously.
Multiple inheritance could also be handled using a similar scheme: each class declares (typedefs) its immediate base classes as a typelist. This results in a type-graph that models the inheritance hierarchy. The sub-graph for a particular visitable class could be walked (at compile time) to generate RTTI like data (tags and convertor functions) that can be used in method forwarding. They are also helpful in generating constant time dynamic casts.