HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

C++ generic double-dispatcher/visitor

Submitted by: @import:stackexchange-codereview··
0
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:

/*
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 :

#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.