I am using a Visitor pattern to traverse and print the children of the tree I am operating on. To get indented printing, I specify the indentation level in a style like:
printCurrent();
indentLevel(); // increases static variable
Visitor::visit(elem); // which then prints the children's node data
unindentLevel(); // decreases static variable
In order to make this nicer, I want to implement a function that takes the Visitor::visit
with the argument elem
and automatically handles the pre-action (indentLevel()
) and post-action (unindentLevel()
).
Before implementing that function, I need to define a function pointer that will be used as argument for the function. However, I am failing at specifying the argument to the pointer. As an example, let's look into the PrintVisitor
which is derived from the Visitor
:
void PrintVisitor::visit(BinaryExpr &elem) {
std::cout << formatOutputStr({elem.getNodeName()});
this->incrementLevel();
Visitor::visit(elem); // <-- this is where I want to create a function pointer to
this->decrementLevel();
}
The idea basically is that the PrintVisitor
does everything related to printing and all other logic (e.g., traversal logic) is implemented in the base class Visitor
. Hence the PrintVisitor::visit
needs to execute its specific action (e.g., printing via formatOutputStr) and then execute the Visitor::visit
method:
void PrintVisitor::visit(BinaryExpr &elem) {
std::cout << formatOutputStr({elem.getNodeName()});
void (Visitor::*myPt)(BinaryExpr&) = &Visitor::visit; // declare function pointer
executeIndented(myPt, elem); // pass function pointer myPt
}
// ...
void executeIndented("Function f", "FunctionArgs elem") {
// pre-action
this->incrementLevel();
// main action: call function pointer
(Visitor().*f)(elem); // call function pointer with arg
//post-action
this->decrementLevel();
}
My goal is to somehow achieve that both pre- and post-action always are called in each PrintVisitor::visit
method. For that I was thinking it would make sense to encapsulate these pre- and post-actions into another function executeIndented
which ensures that.
The syntax of (Visitor().*myPt)(elem);
looks a little odd to me, is this really the correct way to call the (base) function Visitor::visit
with the argument elem
using my function pointer myPt
?
// EDIT
Using (Visitor(*this).*myPt)(elem);
instead also works. What's the difference between those two ways and is either one of those to be preferred?
// EDIT2 Hope that the description of what I am trying to achieve is more clear now.
As I understand, you should have something like:
struct TraversalVisitor : IVisitor
{
void visit(BinaryExpr &elem) final
{
pre_traversal_action(elem);
visit(elem.lhs);
action(elem);
visit(elem.rhs);
post_traversal_action(elem);
}
virtual void pre_traversal_action(BinaryExpr &elem) { /*Nothing */ }
virtual void action(BinaryExpr &elem) { /*Nothing */ }
virtual void post_traversal_action(BinaryExpr &elem) { /*Nothing */ }
void visit(UnaryExpr &elem) final;
// ...
};
struct PrintVisitor : TraversalVisitor
{
void pre_traversal_action(BinaryExpr &elem) override {
std::cout << formatOutputStr({elem.getNodeName()});
incrementLevel();
}
//void action(BinaryExpr &elem) override { /*Nothing */ }
void post_traversal_action(BinaryExpr &elem) override { decrementLevel(); }
// ...
private:
void formatOutputStr(const std::string&);
void incrementLevel();
void decrementLevel();
// ...
};
Whereas you try to implement something like:
struct Visitor : IVisitor
{
virtual visit(BinaryExpr &elem)
{
visit(elem.lhs);
visit(elem.rhs);
}
// ...
};
struct PrintVisitor : Visitor
{
private:
void formatOutputStr(const std::string&);
void incrementLevel();
void decrementLevel();
void executeIndented(Expr& elem) {
incrementLevel(); // pre-action
// Traversal
Visitor::visit(elem);
decrementLevel(); // post-action
}
void visit(BinaryExpr &elem) override {
std::cout << formatOutputStr({elem.getNodeName()});
executeIndented(elem);
}
// ...
};
Your attempt, IMO, just factorize PrintVisitor
, without enforcing some traversal strategy.