Implementing the visitor pattern using C++ Templates

Viewed 17036

I've been trying to reduce the amount of boilerplate in my code, by using C++ Templates to implement the visitor pattern. So far I've come up with this:

class BaseVisitor {
public:
    virtual ~BaseVisitor() {}
};

template<typename T>
class Visitor : public BaseVisitor {
public:
    virtual void visit(T& /* visitable */) = 0;
};

template<typename Derived>
class Visitable {
public:
    void accept(Visitor<Derived>& visitor) {
        visitor.visit(static_cast<Derived&>(*this));
    }
};

And each subclass of Visitable looks like this:

class Mesh : public Object, public Visitable<Mesh> {};
class Text : public Object, public Visitable<Text> {};

And finally the Visitor looks like this:

class Renderer : public Visitor<Mesh>, public Visitor<Text> {}

So far so good... now here's the problem:

for(Scene::iterator it = scene.begin(); it != scene.end(); ++it) {
    Object& object = static_cast<Object&>(*it);
    if(pre_visit(object)) {
        object.accept(this); ///Erm, what do I cast to??
        post_visit(object);
    }
}

I need to somehow cast to Visitable so that I can call accept(), but obviously I don't know what T is. Alternatively I can't add a virtual accept() to the Visitable template, because I don't know what argument it should take.

Any C++ Templating guru's out there know how to make this work?

3 Answers

I was also in need of a templated Visitor pattern, and was able to create a solution that does not involve the usage of variadic types or type lists.

// forward declarations for our Visitable interface
class Object;
class Visitor;

// Visitable objects can accept a visitor.
class Visitable
{
public:
    virtual ~Visitable() { }
    virtual void accept_visitor(Visitor& visitor) = 0;
    virtual void accept(Object& obj);
};

// A base class, to allow downcasting
class Object
{
protected:
    virtual void _f() { }
};

// Our Visitor class, which will wrap our concrete visitor implementation
class Visitor
{
public:
    Visitor(Object* obj);

    // Base class for concrete visitors
    template<typename D, typename V>
    class OfType : public Object
    {
    public:
        void visit(V* visitable) {
            D* derived = static_cast<D*>(this);

            // "duck-typed" method; if our derived class does not have
            // this method, compilation will fail.
            derived->on_visit(visitable);
        }
    };

    template<typename D, typename V>
    void visit(V* visitable);

private:
    Object* m_obj;
};

Visitor::Visitor(Object* obj) : m_obj(obj) { }

template<typename D, typename V>
void Visitor::visit(V* visitable) {
    // check if our visitor is able to visit this instance
    OfType<D,V>* visitor = dynamic_cast<OfType<D,V>* >(m_obj);
    if (visitor) {
        visitor->visit(visitable);
    }
}

void Visitable::accept(Object& visitor) {
    Visitor wrapped(&visitor);
    accept_visitor(wrapped);
}

After the above interfaces are defined, create specific interfaces for a visitable object's visitor, then implement them in your concrete class:

class This;

class ThisVisitor : public Visitor::OfType<ThisVisitor, This>
{
public:
    virtual void on_visit(This* item) = 0;
};

class This : public Visitable
{
public:
    void accept_visitor(Visitor& visitor) {
        visitor.visit<ThisVisitor>(this);
    }
};

class That;

class ThatVisitor : public Visitor::OfType<ThatVisitor, That>
{
public:
    virtual void on_visit(That* item) = 0;
};

class That : public Visitable
{
public:
    void accept_visitor(Visitor& visitor) {
        visitor.visit<ThatVisitor>(this);
    }
};

class MyVisitor : public ThisVisitor, public ThatVisitor
{
public:
    void on_visit(This* item) { printf("This!"); }
    void on_visit(That* item) { printf("That!"); }
};

int main(int argc, const char* argv[] {
    This item1;
    That item2;
    MyVisitor visitor;
    item1.accept(visitor);   // "This!"
    item2.accept(visitor);   // "That!"
}

You could also skip the visitor interfaces entirely and have your concrete visitor derive from OfType<Derived, SomeClass> directly, but I find using the former is better for extending your visitor as new classes are defined (That should not care about who visits it as long as it is of type ThatVisitor).

Related