Visitor with CRTP

I mentioned some code in the Visitor post that allows one to avoid having to write an accept method on all of the subclasses of a Visitable base class. Using the Curiously Recurring Template Pattern to achieve this bit of convenience is not very complicated, as we will soon see. First we need to declare a base class that gives all its subclasses the accept() method. Since we don’t know what visitors will exist in the future, accept() will take a parameter of the base class that all visitors will inherit. Since we haven’t created the visitor base class yet, we forward declare it as VisitorBase.

struct VisitorBase; //forward declaration of our visitor

//write our accept method in the base class
//this is helpful because it allows us to have containers of Visitable objects
struct Visitable
{
 virtual void accept(VisitorBase &v) const = 0;
};

// create the base craft struct which will have our only implementation of the accept() method
template <typename T>
struct Craft : Visitable
{
 unsigned int size, weight, RGBAColor;

 void accept(VisitorBase &v) const
 {
   const T* upcast = static_cast<const T* const>(this);
   v.visit(*upcast);
 }
};

You may notice that we are static casting to the templated type, this is how we get a pointer to the subclass from the base class. It allows us to write the following in the subclass code (notice the inheritance syntax)

struct Plane : Craft<Plane>
{
 Plane(int numberOfPassengers, int numberOfParachutes) :
   numberOfPassengers(numberOfPassengers),
   numberOfParachutes(numberOfParachutes){};
 int numberOfPassengers, numberOfParachutes;
};

struct Boat : Craft<Boat>
{
 Boat(int numberOfLifeVests, int maxNumberOfPassengers) :    
   numberOfLifeVests(numberOfLifeVests), 
   maxNumberOfPassengers(maxNumberOfPassengers){};
 int numberOfLifeVests, maxNumberOfPassengers;
};

The danger here is that if an absent minded developer wrote that Boat inherited Visitable<Plane> then the static cast would claim that a Boat is a plane and whatever lives in the memory of the Boat at the Plane positions would be used when the visitor’s visit(const Plane& p) method is run. Sooooo dangerous! Who know’s what’ll be in that memory?! I think this is the only danger though, so just don’t do that. Hell, be careful when using templates all together. If you want to test for this sort of bad cast, use a dynamic_cast instead of a static_cast and just check for NULL pointers (see attached file at end).

Next we create a visitor named CraftComplianceVisitor. The base class needs to hold all of the types that visitors are allowed to visit and the subclasses must have implementations for those types. In this case we only have two types that a visitor might encounter.

//create our visitor base class
struct VisitorBase
{
 virtual void visit(const Boat& b) = 0;
 virtual void visit(const Plane& p) = 0;
};

//subclass the base class and provide implmentations of the abstract methods
//this class will check for "Craft Safety Compliance"
struct CraftSafetyComplianceVisitor : VisitorBase
{
 bool compliant;

 CraftSafetyComplianceVisitor() : compliant(true){};

 virtual void visit(const Boat& boat) {
   compliant &= boat.numberOfLifeVests >= boat.maxNumberOfPassengers;
 };

 virtual void visit(const Plane& plane) {
   compliant &= plane.numberOfParachutes >= plane.numberOfPassengers;
 };
};

Now all that’s needed is some code that runs it. Maybe something that looks like this

#include "iostream";
int _tmain(int argc, _TCHAR* argv[])
{
 std::vector<Visitable*> crafts;
 crafts.push_back(new Boat(1,1));
 crafts.push_back(new Plane(1,1)); //yes these leak, no it doesn't matter here

 CraftSafetyComplianceVisitor v;
 for(std::vector<Visitable*>::iterator i = crafts.begin(); i != crafts.end(); i++) {
   (*i)->accept(v);
 }

 std::cout << (!v.compliant ? "not " : "") << "all crafts are in compliance" << std::endl;

 return 0;
}

I’ve attached the whole program (with some minor rearranging so that G++ doesn’t choke (MSVC is a bit more forgiving and in this case it makes a difference).

The full cpp file