QuantLib: Visitation – The AcyclicVisitor and the Visitor Template

The visitor design pattern is one of the classic software design patterns. It solves the problem of calling different functions for each objects of different types in a polymorphic collection. Imagine a type hierarchy.

class Base;

class DerivedA : public Base;

class DerivedB : public Base;

class SecondGen : public DerivedA;

Now with this hierarchy we can start creating a polymorphic container. For simplicity assume we are storing bare pointers. (In general you should use smart pointers such as boost::shared_ptr.)

std::vector array;

Now imagine you also have a number of overloaded functions

void doSomething(Base&);
void doSomething(DerivedA&);
void doSomething(DerivedB&);

You would like to iterate through your array and call the appropriate function for each object in the vector. A simple solution would be to attempt to dynamically typecast the pointers to the different derived types and, if successful, call the function with the derived type as argument. But this is extremely awkward. Not only would the casting have to be carried out in the correct order to avoid calling the more general function. In addition the code would have to be repeated for every set of functions one would like to call this way.

One solution to this problem is the visitor design pattern as implemented in QuantLib. The mechanism for calling the functions is transferred inside the class hierarchy. This makes it easier to call the functions but comes at the expense that every class in the hierarchy has to implement one additional method.

Accept and the AcyclicVisitor

QuantLib defines a dummy class called AcyclicVisitor. This class does not implement any methods and has no properties. It’s only purpose is to serve as argument for the accept() method. Take for instance the CashFlow class. It implements the following method.

virtual void accept(AcyclicVisitor &v);

A class can inherit from AcyclicVisitor in order to signal that it implements the visitor design pattern and that it can act as an argument for the accept() method.

The Visitor Template

The actual work is carried out by virtue of the Visitor template.

template<class T>
class Visitor {
  public:
    virtual ~Visitor() {}
    virtual void visit(T&) = 0;
};

The Visitor template defines an abstract method visit() which takes an object of the template type as argument. A class that wants to implement the visitor design pattern should inherit from multiple Visitor classes with different template arguments for the different types which it wants to accept. In addition the class should inherit from the AcyclicVisitor so that it can be passed to the accept() method.

A Simple Example

Assume that you want to perform visitation on CashFlow objects. Cash flows are dealt with in a separate article. You can read more about them here.

The cash flows in QuantLib make up a hierarchy of classes, each defining the virtual accept() method. In this example let us look only at the simple cash flows. Imagine we have a vector of cash flow objects of various types. We would like to add the amounts of all the cash flows of the different types in separate sums. (Note that, in this simplified example we will just simply add the plain amount values without discounting them to any specific date.) We define the class that should do the work.

class AddCashFlowAmounts :
      public AcyclicVisitor,
      public Visitor<CashFlow>,
      public Visitor<SimpleCashFlow>,
      public Visitor<Redemption>,
      public Visitor<AmortizingPayment>
{
  private:
    Real cashFlowSum;
    Real simpleCashFlowSum;
    Real redemptionSum;
    Real amortizingPaymentSum;
  public:

  void visit(CashFlow &c) {
    cashFlowSum += c.amount();
  }

  void visit(SimpleCashFlow &c) {
    simpleCashFlowSum += c.amount();
  }

  void visit(Redemption &c) {
    redemptionSum += c.amount();
  }

  void visit(AmortizingPayment &c) {
    amortizingPaymentSum += c.amount();
  }

  void printSums() {
    std::cout << "Sum of redemptions: " << redemptionSum << std::endl;
    std::cout << "Sum of amortizing payments: " << amortizingPaymentSum << std::endl;
    std::cout << "Sum of other simple cash flows: " << simpleCashFlowSum << std::endl;
    std::cout << "Sum of all other cash flows : " << cashFlowSum << std::endl;
  }
};

With this visitor class defined we can write the code that iterates through our vector and passes the visitor to each one of them.

void calcCashFlowSums(std::vector<boost::shared_ptr<CashFlow> > &cashFlows) {
  AddCashFlowAmounts addCashFlowsVisitor;
  for (int i=0; i<cashFlows.size(); ++i)
    cashFlows[i].accept(addCashFlowVisitor);

  addCashFlowVisitor.printSums();
}

Note that the vector can contain cash flows of any type in the CashFlow hierarchy. If the exact type is not handled a visit method for the parent type is looked for. If that doesn’t exist then a method for the next type up in the hierarchy is looked for, and so on. Because the visitor handles the base type, CashFlow, any object that isn’t handled otherwise will eventually be handled by the void visit(CashFlow &c) method.

Extending The Hierarchy

The ease of writing the above code comes at some expense. If we want to extend the hierarchy of classes that, directly or indirectly, inherit from CashFlow then we have to implement the accept() method for every new class. To see what has to be done, let’s look at the code for Redemption::accept().

inline void Redemption::accept(AcyclicVisitor& v) {
  Visitor<Redemption>* v1 =
    dynamic_cast<Visitor<Redemption>*>(&v);
  if (v1 != 0)
    v1->visit(*this);
  else
    SimpleCashFlow::accept(v);
}

In this method the AcyclicVisitor is dynamically cast into the Visitor type. If successful, then we know that the method visit(Redemption &) exists and we call it, passing the *this object as argument. If the cast was not successful, then we pass the visitor on to the accept() method of the parent class, SimpleCashFlow. The SimpleCashFlow::accept() method will do the same thing, causing all types in the hierarchy to be tried until the base class is reached.

If you intend to expand the hierarchy by another class, you will have to write a similar accept() method. In practice you can just copy and paste the example above and replace Redemption with the new class type and SimpleCashFlow with the parent type of your derived class.

4 Comments

  1. Mauricio Bedoya

    Hello and thanks for providing insight of this issue in Quantlib.
    However, I have two questions:

    – How do you pretend to get CashFlow amounts (this is pure virtual). As I understand, if you don’t visit the base class “CashFlow”, calcCashFlowSums will remain abstract. However, if you visit base class, calcCashFlowSums can be implemented as you want.

    -The code that comes after: ” ….With this visitor class defined we can write the code that iterates through our vector and passes the visitor to each one of them…, is incomplete:

    void calcCashFlowSums(std::vector<boost::shared_ptr > &cashFlows)
    {
    AddCashFlowAmounts addCashFlowsVisitor;
    for (int i=0; iaccept(addCashFlowVisitor);
    }

    addCashFlowVisitor.printSums();
    }

    Many thanks for this wonderful job, and best regards

    Mauricio Bedoya

    Reply
    1. Mikail

      Hi Mauricio

      Regarding your first question, I am never creating an object of type CashFlow directly. All objects have to be descendants of the CashFlow class and implement the abstract methods.

      The cashFlows vector that is passed to the calcCashFlowSums function contains (smart) pointers to objects extending the CashFlow class. The visitor handles three types of cash flows directly, SimpleCashFlow, Redemption and AmortizingPayment. But the cash flows in the vector don’t necessarily have to be any of these three (or descendants thereof). Imaging another cash flow class, FooCashFlow, that inherits directly from CashFlow and implements all abstract methods. Because it doesn’t match any of the specialised visitor methods it will end up calling the void visit(CashFlow &c) method. The method takes a reference to the object and so polymorphism is preserved and the FooCashFlow::amount() method is called.

      Regarding your second comment, thanks for letting me know. I have fixed the code.

      Reply
  2. Mauricio Bedoya

    jejeje Another try to get the code correct

    <void resutls(vector<boost::shared_ptr >& cal)
    {
    Asd visitorOperations;
    for(int i= 0; i

    Reply
  3. function

    5 Key Stretches for Runners | Ocean City Running Club

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>