Templated implementation of the Visitor Pattern

Introduction

There are probably many of you out there who, like me, have investigated the possibilities that templates and template meta-programming can bring to your code.

After having some success with templated image and image algorithm classes, I turned my attention to seeing whether some of the design patterns I use could be encapsulated in the same way. Investigation on the web revealed that template meta-programming could play a part in achieving this.

An article on Dr Dobbs introduced me to typelists and some of the meta-programming techniques, with the Visitor pattern used as an example.

Unfortunately, I soon found that Visual C++ was not entirely happy with the syntax used and it took some time before I managed to get a version that didn’t give the compiler indigestion! That didn’t last long, though. Instantiating my first Visitor object caused the compiler to generate the dreaded “Internal compiler error. Try simplifying your code”. Well, I gave up at that point, deciding I had better things to do with my life than battling my compiler. So, I decided to try and achieve the similar objective to the Visitor example without meta-programming.

Anyone unfamiliar with the Visitor pattern should read this first.

The objective of the Dr Dobbs example was merely to remove a small amount of boilerplate code and ensure that all handling omissions in all code would be flagged at compile time.

The use of this technique in the Visitor pattern may seem somewhat trivial, but I hope that you may find the methods used useful in more complex situations, despite it not being as ‘cool’ as using typelists and meta-programming.

How It Works

The purpose of these classes is to create a Visitor base class with pure virtual Visit functions for each supplied type. Any derived class that tries to instantiate an object from it will then be forced to supply an overridden version for each and every Visit function.

Similarly the Visitable class defines pure virtual Accept function for each supplied visitor type.

The framework is based on two template classes; Visitor & Visitable.

Visitable

Classes that should be visitable should derive from the Visitable template class. The classes that may visit should be defined in the template type parameter list.
Here I have defined a template and specialisations to accept up to 4 visitor types.

//*****************************************************************
/// The visitable base class for four visitor types.
//*****************************************************************
template <typename T1, typename T2 = void, typename T3 = void, typename T4 = void>
class Visitable
{
public:

  virtual void Accept(T1&) = 0;
  virtual void Accept(T2&) = 0;
  virtual void Accept(T3&) = 0;
  virtual void Accept(T4&) = 0;
};

//*****************************************************************
/// The visitable base class for three visitor types.
//*****************************************************************
template <typename T1, typename T2, typename T3>
class Visitable<T1, T2, T3>
{
public:

  virtual void Accept(T1&) = 0;
  virtual void Accept(T2&) = 0;
  virtual void Accept(T3&) = 0;
};

//*****************************************************************
/// The visitable base class for two visitor types.
//*****************************************************************
template <typename T1, typename T2>
class Visitable<T1, T2>
{
public:

  virtual void Accept(T1&) = 0;
  virtual void Accept(T2&) = 0;
};

//*****************************************************************
/// The visitable base class for one visitor type.
//*****************************************************************
template <typename T1>
class Visitable<T1>
{
public:

  virtual void Accept(T1&) = 0;
};

Visitor

Classes that should be able to visit visitable classes should derive from the Visitor template class. The types that the visitor expects to support should be defined in the template type parameter list.

Here I have defined a template and specialisations to accept up to 16 types types. It may seem like it would be an onerous task to modify this to accept any N types, but the repetitive nature of the code lends itself perfectly to generation via a script.

//*****************************************************************
/// The visitor base class for sixteen types.
//*****************************************************************
template <typename T1,         typename T2  = void, typename T3  = void, typename T4  = void,
          typename T5  = void, typename T6  = void, typename T7  = void, typename T8  = void,
          typename T9  = void, typename T10 = void, typename T11 = void, typename T12 = void,
          typename T13 = void, typename T14 = void, typename T15 = void, typename T16 = void>
  class Visitor
{
public:

  virtual void Visit(T1&) = 0;
  virtual void Visit(T2&) = 0;
  virtual void Visit(T3&) = 0;
  virtual void Visit(T4&) = 0;
  virtual void Visit(T5&) = 0;
  virtual void Visit(T6&) = 0;
  virtual void Visit(T7&) = 0;
  virtual void Visit(T8&) = 0;
  virtual void Visit(T9&) = 0;
  virtual void Visit(T10&) = 0;
  virtual void Visit(T11&) = 0;
  virtual void Visit(T12&) = 0;
  virtual void Visit(T13&) = 0;
  virtual void Visit(T14&) = 0;
  virtual void Visit(T15&) = 0;
  virtual void Visit(T16&) = 0;
};

//*****************************************************************
/// The visitor base class for fifteen types.
//*****************************************************************
template <typename T1,  typename T2,  typename T3,  typename T4,
          typename T5,  typename T6,  typename T7,  typename T8,
          typename T9,  typename T10, typename T11, typename T12,
          typename T13, typename T14, typename T15>
  class Visitor<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
{
public:

  virtual void Visit(T1&) = 0;
  virtual void Visit(T2&) = 0;
  virtual void Visit(T3&) = 0;
  virtual void Visit(T4&) = 0;
  virtual void Visit(T5&) = 0;
  virtual void Visit(T6&) = 0;
  virtual void Visit(T7&) = 0;
  virtual void Visit(T8&) = 0;
  virtual void Visit(T9&) = 0;
  virtual void Visit(T10&) = 0;
  virtual void Visit(T11&) = 0;
  virtual void Visit(T12&) = 0;
  virtual void Visit(T13&) = 0;
  virtual void Visit(T14&) = 0;
  virtual void Visit(T15&) = 0;
};

//*****************************************************************
// The same pattern is repeated for each decreasing number of parwameters until...
//*****************************************************************

//*****************************************************************
/// The visitor base class for one type.
//*****************************************************************
template <typename T1>
class Visitor<T1>
{
public:

  virtual void Visit(T1&) = 0;
};

How It’s Used

I’ll use the good old ‘Shape’ example so loved by many.

First, you create the base for your ‘Shape’ visitor.

//*****************************************************************
// Pre-declare the shapes.
//*****************************************************************
class Square;
class Circle;
class Triangle;

//*****************************************************************
// The shape visitor base class.
// Pure virtual 'Visit' functions will be defined for the Square,
// Circle, and Triangle types.
//*****************************************************************
class ShapeVisitor : public Visitor<Square, Circle, Triangle>
{
};

Then, you define the ‘Shape’ base class. It derives from the Visitable class that defines a pure virtual ‘Accept’ function that accepts a ShapeVisitor.

//*****************************************************************
// The shape base class.
//*****************************************************************
class Shape : public Visitable<ShapeVisitor>
{
};

Next, you define the shapes ‘Square’, ‘Circle’, and ‘Triangle’. Each overrides the ‘Accept’ function that calls the visitor with itself as a parameter.

//*****************************************************************
// The square class
//*****************************************************************
class Square : public Shape
{
  void Accept(ShapeVisitor& visitor)
  {
    visitor.Visit(*this);
  }
};

//*****************************************************************
// The circle class
//*****************************************************************
class Circle : public Shape
{
  void Accept(ShapeVisitor& visitor)
  {
    visitor.Visit(*this);
  }
};

//*****************************************************************
// The triangle class
//*****************************************************************
class Triangle : public Shape
{
  void Accept(ShapeVisitor& visitor)
  {
    visitor.Visit(*this);
  }
};

Now that you have the framework in place, you can do something with it. Here’s an example that creates ‘Draw’ and ‘Serialise’ visitors and applies them to a vector of Shape objects.

//*****************************************************************
// The 'draw' visitor.
//*****************************************************************
class DrawVisitor : public ShapeVisitor
{
public:
    void Visit(Square& square)
    {
        std::cout << "Draw the square\n";
    }

    void Visit(Circle& circle)
    {
        std::cout << "Draw the circle\n";
    }

    void Visit(Triangle& triangle)
    {
        std::cout << "Draw the triangle\n";
    }
};

//*****************************************************************
// The 'serialise' visitor.
//*****************************************************************
class SerialiseVisitor : public ShapeVisitor
{
public:
    void Visit(Square& square)
    {
        std::cout << "Serialise the square\n";
    }

    void Visit(Circle& circle)
    {
        std::cout << "Serialise the circle\n";
    }

    void Visit(Triangle& triangle)
    {
        std::cout << "Serialise the triangle\n";
    }
};

Now we define the actual visitors and a container for shapes.

//*****************************************************************
// The actual visitors.
//*****************************************************************
DrawVisitor      draw_visitor;
SerialiseVisitor serialise_visitor;

//*****************************************************************
// The list of shapes.
//*****************************************************************
std::vector<Shape*> shape_list;

The following is a utility function to apply a visitor to the list of shapes.

//*****************************************************************
// The Apply a visitor.
//*****************************************************************
void Apply(ShapeVisitor &visitor)
{
  for (std::vector<Shape*>::size_type i = 0; i < shape_list.size(); ++i)
  {
    // Send the visitor to the shape.
    shape_list[i]->Accept(visitor);
  }  
}

And now the code to test in all.


//*****************************************************************
// Main
//*****************************************************************
int main()
{
  // Create some shapes.
  Square   square;
  Circle   circle;
  Triangle triangle;

  // Add them to the vector
  shape_list.push_back(&square);
  shape_list.push_back(&circle);
  shape_list.push_back(&triangle);

  // Apply the visitors.
  Apply(draw_visitor);
  Apply(serialise_visitor);

  return 0;
}

The output from the example is as follows…

Draw the square
Draw the circle
Draw the triangle
Serialise the square
Serialise the circle
Serialise the triangle

A version of this framework can be found as part of the Embedded Template Library, an open source C++ library distributed under the MIT licence.

John Wellbelove

John Wellbelove

Director of Aster Consulting Ltd
United Kingdom
I have been involved in technology and computer systems for all of my working life and have amassed considerable knowledge of designing and implementing systems that are both performant and correct. My role normally encompasses the entire project life-cycle, from specification to maintenance phase. Most systems I have worked on have required high speed and deterministic performance, often within a highly constrained platform. I am experienced in designing and adapting algorithms to solutions that are both space and time efficient, avoiding the normal overheads of standard solutions. Acting as a mentor for colleagues has often been a significant, though unofficial, part of my role.

I administer an open source project on Github.
See http://www.etlcpp.com/

1 comment on Templated implementation of the Visitor Pattern

Leave a Reply

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