Original Post
I'm working on a set of C++ classes that represent nodes in a tree. I want to be able to traverse that tree and apply logic to each node, basically as a Visitor pattern: This works great and accomplishes precisely what I want it to. There is, however, one serious drawback: the Node classes are now dependent on the actual "traverser" class. In this case, you can see the ValidationTraverser class showing up all over the place. I want to add several other types of traverser classes over time, which means I'd have to add overloads for all of those traverser types in the TraversableBase/Traversable interfaces. Whew! So... all that to say, is there a better way to accomplish this? Or is it just yet another corner case where C++ kind of sucks?
template <typename T>
void Node::Visit(T& visitor)
{
visitor.DoStuff();
} However, I also want to parameterize the actual visitor call, so that it can do different things based on the specific Node subclass that it's been issued: class Visitor
{
public:
template <typename T>
void DoStuffWithNode(T& node)
{
T.DoSomething();
}
}; In order to pull this off, I've introduced CRTP to allow the node classes to pass their own types into the visitor: class Traverser
{
public:
template <typename T>
void DoStuff(T& node)
{
node.Stuff();
}
};
template <typename SelfT>
class SelfAware
{
protected:
SelfT& GetSelf() { return *static_cast<SelfT*>(this); }
};
class SomeNode : public SelfAware<SomeNode>
{
template <typename T>
void Traverse(T& traverser)
{
traverser.DoStuff(GetSelf());
}
}; Now it gets messy. I need to be able to store base Node pointers, which means I lose the type information about the derived node subclasses. The vast majority of things that the Node does happen through virtual functions. What I've run into is that it's nasty to try and mix CRTP and base class pointers. The best solution I've come up with is to have a base TraversableBase class which has a pure virtual function for the "Do Stuff" side of things. I then have a template Traversable<> which inherits from TraversableBase and provides the CRTP parameter. The final result looks thusly: class ValidationTraverser
{
public:
template <class OperationClass>
void TraverseNode(const OperationClass& op)
{
TaskSafetyCheck(op, *this);
}
// ... etc.
};
class TraversableBase
{
public:
virtual ~TraversableBase()
{ }
virtual void Traverse(Validator::ValidationTraverser& traverser) = 0;
};
template <class SelfType>
class Traversable : public TraversableBase
{
public:
virtual void Traverse(Validator::ValidationTraverser& traverser)
{
traverser.TraverseNode(*static_cast<SelfType*>(this));
}
};