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?
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.
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.
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.
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.
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); } }
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)); } };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With