Search code examples
c++function-pointers

Call base method from base function pointer with arguments


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.


Solution

  • 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.