Implementing the visitor pattern using C++ Templates
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);
}
}
Implement the visitor pattern with templates
Like user786653 says, the Curiously Recurring Template Pattern can solve this
template<typename Visitor, typename Derived>
struct ElementBase : public Base
{
void visit()
{
_e.visit(static_cast<Derived*>(this));
}
private:
Visitor _e;
};
// Atoms.
template<typename Visitor>
struct ElementA : public ElementBase<Visitor, ElementA<Visitor> >
{
ElementA() : a(5) {}
int a;
};
// Visitors.
struct VisitorA
{
void visit(ElementA<VisitorA> *a)
{
cout << a->a << endl;
}
};
How can I implement the visitor patter with return type in C++
If a visitor needs to return a value, it is normal to store the returned value in the visitor itself. Thus:
NodeVisitor<double> dv;
node->accept(dv);
double result = dv.result();
If you don't like the boilerplate, you can wrap it in a non-virtual member:
class model::VisitableNode {
public:
template<class T>
/* non-virtual */ T accept(NodeVisitor<T>& v) {
do_accept(v);
return v.result;
}
virtual void do_accept(NodeVisitorBase& v) = 0;
}
Generic visitor base class template in C++ - overload issue
The compiler doesn't know which base-class' visit
function to call. See this question of mine. As such, as you correctly said, you need to make the functions available in the visitor
class with using
declarations. Sadly, you can't just use using visitor_base<T>::visit...;
, as that is not a valid pattern. You have to recursively inherit from one base after another and everytime bring the base-class visit
s into the scope of the derived class:
template <typename T>
struct visitor_base {
virtual void visit(T&) {};
};
template <typename Head, typename... Tail>
struct recursive_visitor_base
: visitor_base<Head>
, recursive_visitor_base<Tail...>
{
using visitor_base<Head>::visit;
using recursive_visitor_base<Tail...>::visit;
};
template<class T>
struct recursive_visitor_base<T>
: visitor_base<T>
{
using visitor_base<T>::visit;
};
template <typename... T>
struct visitor
: recursive_visitor_base<T...>
{
using recursive_visitor_base<T...>::visit;
};
Live example on Ideone (I had to tweak the partial spec a bit, since GCC 4.5.1 is a bit buggy in that part. Clang compiles the code shown in this answer just fine). Output:
that was a base.
that was 42
that was 2.79
that was 2.79
that was a base.
=================
that was a base.
that was 1804289383
that was 8.46931e+08
that was 1.68169e+09
that was a base.
C++ Visitor Pattern with variable return types
Several options:
Don't return anything, handle the value inside the visitor. The visitor is completely free to store or handle any return value and type.
Return a variadic type (
std::any
orstd::variant
). Note that using them requires you to find out the actual type, which is kind-of similar to your original problem perhaps.Inherit multiple
IVisitor<>
interfaces. This might be the easiest way even, although it's not necessary elegant.
visitor pattern for template derived classes
The problem is your Visitor
class is tightly coupled with classes that derive from Element
. As you expand your design this is going to get in the way more than it already is. You can reduce/eliminate the right coupling by providing a "destination" class that defines all the requirements of a visitable object. Since the name of a derived classes is a common attribute you can place the storage and access to it into the destination class as well.
// 1. Define out visitor and destination interfaces
struct Destination
{
Destination(const std::string& name) : name_(name) {}
virtual std::string ident() const { return name_; }
const std::string name_;
};
struct Visitor
{
virtual void visit(Destination *e) = 0;
};
This keeps the requirements of the visitor separate from the Element class which seems to be your intention. Then your This
and That
classes inherit from Destination
and provide the necessary implementations.
// 2. Define our element and it's derived classes
class Element
{
public:
virtual void accept(class Visitor &v) = 0;
};
template <unsigned int N>
class This: public Element, public Destination
{
public:
This() : Destination("This") {}
virtual void accept(Visitor &v)
{
v.visit(this);
}
};
class That: public Element, public Destination
{
public:
That() : Destination("That") {}
virtual void accept(Visitor &v)
{
v.visit(this);
}
};
Now your up and down visitors are simplified into something like the following
// 3. Create a "visitor" derived class for each "operation" to do on "elements"
class UpVisitor: public Visitor
{
void visit(Destination *e) {
cout << "do Up on " + e->ident() << '\n';
}
};
class DownVisitor: public Visitor
{
void visit(Destination *e) {
cout << "do Down on " + e->ident() << '\n';
}
};
Although I did not change it in the solution above I recommend changing visit
to take a reference instead of a pointer. Since C++ has no notion of a null reference this indicates that Destination
is required where as a pointer could be considered optional.
Related Topics
Serial Port (Rs -232) Connection in C++
How to Typedef a Template Class
Iter_Swap() Versus Swap() -- What's the Difference
How to Forward Declare a Template Class in Namespace Std
Where Does One Get the "Sys/Socket.H" Header/Source File
Linear Index Upper Triangular Matrix
What Is Use of the Ref-Qualifier 'Const &&'
Why Aren't Copy Constructors "Chained" Like Default Constructors and Destructors
Cannot Open Include File: 'Ctype.H': No Such File or Directory
Lru Implementation in Production Code
How to Assign Multiple Values into a Struct at Once
No == Operator Found While Comparing Structs in C++
Qt: How to Handle the Event of the User Pressing the 'X' (Close) Button
Idiomatic Way to Declare C++ Immutable Classes