Templated Visitor Base Class

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.

Ok, it's time to start.

The Classes

This is the null type. It is merely there to act as a place-holder for default template parameters. It's a template structure that is parameterised with an integer id.

//*****************************************************************
// The null type class.
// For default template parameters.
//*****************************************************************
template <const int id>
struct Null_Type
{
};

This is the class used for actual visitor template parameter types. It's a pure virtual class and requires a derived class to override the Visit function.

//*****************************************************************
// The virtual visit class.
// Declares a pure virtual visit function for the type.
//*****************************************************************
template<typename T>
class Visit_Type
{
public:
    virtual Visit(T &) = 0;
};

This is a specialised class for the default null visit types. It defines a concrete non-virtual protected function. Derived classes do not need to override it.

//*****************************************************************
// Specialised visit class.
// Declares a non-virtual concrete visit function for null
// visitors.
//*****************************************************************
template <const int id>
class Visit_Type<Null_Type<id> >
{
protected:
    void Visit(Null_Type<id> &);
}

Templated Visitor Base Class

This is the visitor base class. The class has been designed to accommodate up to 20 parameters. Add extra capacity as required.

//**************************************************************
// The Visitor base class.
// Accommodates up to 20 types.
//**************************************************************
template <typename T1,
          typename T2  = Null_Type<2>,
          typename T3  = Null_Type<3>,
          typename T4  = Null_Type<4>,
          typename T5  = Null_Type<5>,
          typename T6  = Null_Type<6>,
          typename T7  = Null_Type<7>,
          typename T8  = Null_Type<8>,
          typename T9  = Null_Type<9>,
          typename T10 = Null_Type<10>,
          typename T11 = Null_Type<11>,
          typename T12 = Null_Type<12>,
          typename T13 = Null_Type<13>,
          typename T14 = Null_Type<14>,
          typename T15 = Null_Type<15>,
          typename T16 = Null_Type<16>,
          typename T17 = Null_Type<17>,
          typename T18 = Null_Type<18>,
          typename T19 = Null_Type<19>,
          typename T20 = Null_Type<20> >
class Visitor : public Visit_Type<T1>,
                       Visit_Type<T2>,
                       Visit_Type<T3>,
                       Visit_Type<T4>,
                       Visit_Type<T5>,
                       Visit_Type<T6>,
                       Visit_Type<T7>,
                       Visit_Type<T8>,
                       Visit_Type<T9>,
                       Visit_Type<T10>,
                       Visit_Type<T11>,
                       Visit_Type<T12>,
                       Visit_Type<T13>,
                       Visit_Type<T14>,
                       Visit_Type<T15>,
                       Visit_Type<T16>,
                       Visit_Type<T17>,
                       Visit_Type<T18>,
                       Visit_Type<T19>,                  
                       Visit_Type<T20>
{
public:
    virtual ~Visitor()
    {
    }

    using Visit_Type<T1>::Visit;
    using Visit_Type<T2>::Visit;
    using Visit_Type<T3>::Visit;
    using Visit_Type<T4>::Visit;
    using Visit_Type<T5>::Visit;
    using Visit_Type<T6>::Visit;
    using Visit_Type<T7>::Visit;
    using Visit_Type<T8>::Visit;
    using Visit_Type<T9>::Visit;
    using Visit_Type<T10>::Visit;
    using Visit_Type<T11>::Visit;
    using Visit_Type<T12>::Visit;
    using Visit_Type<T13>::Visit;
    using Visit_Type<T14>::Visit;
    using Visit_Type<T15>::Visit;
    using Visit_Type<T16>::Visit;
    using Visit_Type<T17>::Visit;
    using Visit_Type<T18>::Visit;
    using Visit_Type<T19>::Visit;
    using Visit_Type<T20>::Visit;
};

Classes that want to be visitable should derive from the following class. It ensures that all derived classes must override the 'Accept' function.

//*****************************************************************
// The Visitable base class.
// Parameterised by the visitor type.
//*****************************************************************
template <typename TVisitor>
class Visitable
{
public:
   virtual ~Visitable()
   {
   }

   // Pure virtual 'Accept' function that all visitable classes
   // must override.
   virtual void Accept(TVisitor &visitor) = 0;
};

Templated Visitor Base Class

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.

The Visitor class derives from multiple Visit_Type template classes, one for each template parameter. Null visit types (the default type) will be caught by the specialised Visit_Type that will define a protected concrete non-virtual Visit function. The supplied template parameters, on the other hand, will create pure virtual Visit functions.

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 Shape_Visitor : 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 Shape_Visitor.

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

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(Shape_Visitor &visitor)
    {
        visitor.Visit(*this);
    }
};

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

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

Templated Visitor Base Class

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 Draw_Visitor : public Shape_Visitor
{
public:
    void Visit(Square &square)
    {
        cout << "Draw the square\n";
    }

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

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

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

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

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

//*****************************************************************
// The actual visitors.
//*****************************************************************
Draw_Visitor      draw_visitor;
Serialise_Visitor serialise_visitor;

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

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

//*****************************************************************
// 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

If you're having trouble getting some of the latest C++ techniques to compile, maybe some of this may be helpful to you.

Thanks for reading.



About the Author

John Wellbelove

John has been in the electronics business for 30 years, gradually moving over the years from pure hardware design to mostly software with hardware interface elements in a real-time environment. Over the years he has programmed in Assembler (6502, 6809, 68000), BASIC, Pascal, C & C++. He has dabbled a little in PHP/MySQL as he runs run a climbing club website and forum. http://www.southamptonrats.org. Projects have included DCT based image compression (pre-jpeg), remote imaging security systems, CCTV cameras, images analysis, real-time conveyor control. He is currently working on a template library based on the STL for images and image algorithms.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Hundreds of millions of users have adopted public cloud storage solutions to satisfy their Private Online File Sharing and Collaboration (OFS) needs. With new headlines on cloud privacy issues appearing almost daily, the need to explore private alternatives has never been stronger. Join ESG Senior Analyst Terri McClure and Connected Data in this on-demand webinar to take a look at the business drivers behind OFS adoption, how organizations can benefit from on-premise deployments, and emerging private OFS …

Most Popular Programming Stories

More for Developers

RSS Feeds