Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing the visitor pattern using C++ Templates

Tags:

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?

like image 290
Kazade Avatar asked Aug 03 '12 12:08

Kazade


People also ask

What is a template in C programming?

Templates are a feature of the C++ programming language that allows functions and classes to operate with generic types. This allows a function or class to work on many different data types without being rewritten for each one.

Can we use template in C?

The main type of templates that can be implemented in C are static templates. Static templates are created at compile time and do not perform runtime checks on sizes, because they shift that responsibility to the compiler.

What is template C++ example?

A template is a simple yet very powerful tool in C++. The simple idea is to pass data type as a parameter so that we don't need to write the same code for different data types. For example, a software company may need to sort() for different data types.

How do CPP templates work?

Class Templates in C++ The class templates in C++ make the class using it generic. It operates similar to function templates except that it is used on the entire class instead of a single function. Once the class is created, you can declare the data type while declaring the class instance.


2 Answers

This can be done in C++11 using variadic templates. Continuing from Pete's answer:

// Visitor template declaration template<typename... Types> class Visitor;  // specialization for single type     template<typename T> class Visitor<T> { public:     virtual void visit(T & visitable) = 0; };  // specialization for multiple types template<typename T, typename... Types> class Visitor<T, Types...> : public Visitor<Types...> { public:     // promote the function(s) from the base class     using Visitor<Types...>::visit;      virtual void visit(T & visitable) = 0; };  template<typename... Types> class Visitable { public:     virtual void accept(Visitor<Types...>& visitor) = 0; };  template<typename Derived, typename... Types> class VisitableImpl : public Visitable<Types...> { public:     virtual void accept(Visitor<Types...>& visitor) {         visitor.visit(static_cast<Derived&>(*this));     } }; 

Subclasses of Visitable:

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

A Visitor subclass:

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

It's not clear what the value_type of your Scene container is but you need to obtain a reference or pointer to Visitable<Mesh, Text> on which to call accept:

for(Scene::iterator it = scene.begin(); it != scene.end(); ++it) {     Visitable<Mesh, Text>& object = static_cast<Visitable<Mesh, Text>&>(*it);     if(pre_visit(object)) {         object.accept(*this);         post_visit(object);     } } 
like image 173
Andrew Durward Avatar answered Sep 25 '22 05:09

Andrew Durward


Your BaseVisitor does nothing for you, other than allowing arbitrary visitees to delete the visitor. Instead, you want to have a base class for the visitor which provides all of the different accept functions that could be called on it, and for the Visitable to accept this visitor.

To do this, you could use a type list to define the types the visitor can accept, have a base visitee class which takes the type list, and add the type list as a parameter to your visitee implementation.

sketch of example:

// assuming a typelist has typedefs first and second and a  // type 'empty' representing end of type list  template<typename Types> class Visitor : public Visitor<Types::second> { public:     // visitor has a visit function for each type in Types     virtual void visit(typename Types::first& visitable) = 0; };  template<> class Visitor<empty> { };  template<typename Types> class Visitable{     public:     // base accepts a visitor which can visit any type in Types     virtual void accept(Visitor<Types>& visitor) = 0; };  template<typename Derived, typename Types> class VisitableImpl : public Visitable<Types> { public:     // impl calls specific visit function      virtual void accept(Visitor<Types>& visitor) override {         visitor.visit(static_cast<Derived&>(*this));     } }; 
like image 34
Pete Kirkham Avatar answered Sep 22 '22 05:09

Pete Kirkham