I implemented the visitor pattern according to cpppaterns.com to traverse a tree and apply specific actions based on the node types. I always need to traverse the tree in the same order, hence I moved the logic which determines which child to visit next, into the base class Visitor
. The individual visitors perform specific operations on the tree nodes and then call the visit
method in the base Visitor
. This works pretty well so far.
In some cases it is needed to perform actions implemented among multiple visitors (e.g., VisitorRewriteX
, VisitorPrintX
) on one tree. The naive way is to just execute the visitors sequentially. However, this requires traversing the tree multiple times which is inefficient.
Of course, I could also create a new visitor (e.g., VisitorRewritePrintX
) which just calls the other two visitors—but I think that's not clean code and not flexible. Is there some more generic way (e.g., a pattern) which could help me to somehow allow 'stacking' of the actions implemented in the different visitors?
There are various ways how you can do it, one way would be to have a general-purpose, visitor for which you can register handlers that are called for each visit:
struct visitor {
visitor(std::initializer_list<std::function<void(int)>> cbs) : callbacks(cbs) {
}
void visit(int val) {
for( auto &cb: callbacks ) {
cb(val);
}
}
std::vector<std::function<void(val)>> callbacks;
};
As you have visit
function for different elements, you might need to change that to a class that has the corresponding member function instead of using std::function
Another way would be to utilize variadic template arguments:
struct handler_a {
static void visit(int val) {
std::cout << "handler_a: " << val << std::endl;
}
};
struct handler_b {
static void visit(int val) {
std::cout << "handler_b: " << val << std::endl;
}
};
template <class ...Handlers>
struct visitor {
void visit(int val) {
(Handlers::visit(val), ...);
}
};
int main() {
visitor<handler_a,handler_b> v;
v.visit(10);
return 0;
}
The variadic approach has the advantage that there is no loop because the compiler creates the code for the calls at compile time. But it has the downside that you cannot save temporary information with those handlers while traversing, as those are static functions. But you could maybe solve that with a context object you pass along with the traversed object.
And if you want you can alias your visitor sets with using visitor_a_b = visitor<handler_a,handler_b>
.
I left out the inheritance to keep the code minimal, so you need to add them back to get virtual void accept(visitor& v) override
working.