patterncppMinor
C++ generic double-dispatcher/visitor
Viewed 0 times
genericvisitordoubledispatcher
Problem
I'm trying to reduce the boilerplate code for the visitor/double dispatcher pattern. First I've created a way to enforce the dispatcher to declare the dispatch(T) function for every type supported and also a dispatch_gen(T) for the generic case which I don't need to implement. This is not too hard:
The dispatch_id() is just a hacky way to get the base class, it server as a tag. Now the base class for a visitable/dispatchable:
This gives me a relatively "nice" usage. Here's a simple example:
/*
A generic way to get the type of a base class using a tag function
*/
template
T base_of(U T::*);
/*
Generic dispatcher
*/
template
struct double_dispatcher_impl;
template
struct double_dispatcher_impl {
virtual void dispatch(Head*) = 0;
};
template
struct double_dispatcher_impl : public double_dispatcher_impl {
using double_dispatcher_impl::dispatch;
virtual void dispatch(Head*) = 0;
};
template
struct double_dispatcher;
template
struct double_dispatcher : public double_dispatcher_impl {
using Base = decltype(base_of(&Head::dispatch_id));
using double_dispatcher_impl::dispatch;
virtual void dispatch_gen(Base* b) { b->dispatch(this); }
};The dispatch_id() is just a hacky way to get the base class, it server as a tag. Now the base class for a visitable/dispatchable:
template
struct double_dispatchable {
virtual void dispatch(D* d) { }
void dispatch_id() { }
};This gives me a relatively "nice" usage. Here's a simple example:
struct Circle;
struct Rect;
struct Triangle;
typedef double_dispatcher shape_disp;
struct Shape : public double_dispatchable { };
struct Circle : public Shape { void dispatch(shape_disp* disp) { disp->dispatch(this); } };
struct Rect : public Shape { void dispatch(shape_disp* disp) { disp->dispatch(this); } };
struct Triangle : public Shape { void dispatch(shape_disp* disp) { disp->dispatch(this); } };
struct my_dispatcher : public shape_disp {
void dispatch(Circle* c) { std::cout << "Circle!" << std::endl; }
void dispatch(Rect* c) { std::cout << "Rect!" << std::endl; }
void dispatch(Triangle* c) { std::cout << "Triangle!" << std::endl; }
};Solution
I have implemented something like that in one of my projects, the trick is to use CRTP to pass the Child type to the parent.
This is a simplified code :
With all shapes defined like:
Of course, all the corresponding
I am not very familiar with template meta-programming, so I couldn't find a clean solution to replace the
Furthermore, I don't know if a good compiler can optimize virtual calls using de-virtualization (that's why I tried to add final keyword).
This is a simplified code :
#include "collisions.h"
#define SHAPES Box, Sphere, Mesh
class Box;
class Sphere;
class Mesh;
class Geometry;
// Collidable defines all the pure virtual functions.
template
struct Collidable;
template <>
struct Collidable<> {
virtual bool collide(const Geometry* obj) const = 0 ;
};
template
struct Collidable : Collidable {
using Collidable::collide;
virtual bool collide(const Shape* obj) const = 0;
};
// Geometry inherits from Collidable and is our common base class.
class Geometry : public Collidable {
public:
Geometry();
virtual ~Geometry();
glm::vec3 getPosition() const;
private:
glm::vec3 m_pos;
};
// Dispatcher inherits from Geometry and implements one dispatch function
// for all the given type and the Geometry one.
template
struct Dispatcher;
template
struct Dispatcher : public Geometry {
bool collide(const Geometry* obj) const final override {
return obj->collide(static_cast(this));
}
};
template
struct Dispatcher : public Dispatcher {
using Dispatcher::collide;
bool collide(const Shape* obj) const final override {
return Collisions::collide(obj, static_cast(this));
}
};
// Shape is our generic dispatch class.
template
class Shape : public Dispatcher {};With all shapes defined like:
class Sphere : public Shape {...};Of course, all the corresponding
Collisions::collide(,) must be declared.I am not very familiar with template meta-programming, so I couldn't find a clean solution to replace the
#define SHAPES Box, Sphere, Mesh and the classes declarations at the beginning of the file.Furthermore, I don't know if a good compiler can optimize virtual calls using de-virtualization (that's why I tried to add final keyword).
Code Snippets
#include "collisions.h"
#define SHAPES Box, Sphere, Mesh
class Box;
class Sphere;
class Mesh;
class Geometry;
// Collidable defines all the pure virtual functions.
template <typename... Shapes>
struct Collidable;
template <>
struct Collidable<> {
virtual bool collide(const Geometry* obj) const = 0 ;
};
template <typename Shape, typename... Others>
struct Collidable<Shape, Others...> : Collidable<Others...> {
using Collidable<Others...>::collide;
virtual bool collide(const Shape* obj) const = 0;
};
// Geometry inherits from Collidable and is our common base class.
class Geometry : public Collidable<SHAPES> {
public:
Geometry();
virtual ~Geometry();
glm::vec3 getPosition() const;
private:
glm::vec3 m_pos;
};
// Dispatcher inherits from Geometry and implements one dispatch function
// for all the given type and the Geometry one.
template <class T, typename... Shapes>
struct Dispatcher;
template <class T>
struct Dispatcher<T> : public Geometry {
bool collide(const Geometry* obj) const final override {
return obj->collide(static_cast<const T*>(this));
}
};
template <class T, typename Shape, typename... Others>
struct Dispatcher<T, Shape, Others...> : public Dispatcher<T, Others...> {
using Dispatcher<T, Others...>::collide;
bool collide(const Shape* obj) const final override {
return Collisions::collide(obj, static_cast<const T*>(this));
}
};
// Shape<T> is our generic dispatch class.
template <class T>
class Shape : public Dispatcher<T, SHAPES> {};Context
StackExchange Code Review Q#155863, answer score: 4
Revisions (0)
No revisions yet.