3
\$\begingroup\$

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 <typename T, typename U>
T base_of(U T::*);

/*
Generic dispatcher
*/
template <typename... T>
struct double_dispatcher_impl;

template <typename Head>
struct double_dispatcher_impl<Head> {
    virtual void dispatch(Head*) = 0;
};

template <typename Head, typename... Tail>
struct double_dispatcher_impl<Head, Tail...> : public double_dispatcher_impl<Tail...> {
    using double_dispatcher_impl<Tail...>::dispatch;

    virtual void dispatch(Head*) = 0;
};

template <typename... T>
struct double_dispatcher;

template <typename Head, typename... Tail>
struct double_dispatcher<Head, Tail...> : public double_dispatcher_impl<Head, Tail...> {
    using Base = decltype(base_of(&Head::dispatch_id));
    using double_dispatcher_impl<Head, Tail...>::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 <typename D>
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<Circle, Rect, Triangle> shape_disp;

struct Shape : public double_dispatchable<shape_disp> { };
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; }
};

Now there's still a lot of repetitive code in there (the callbacks for the subclasses). How could I improve the pattern? Can I remove the repetitive code with metaprogramming and generics, without using macros?

\$\endgroup\$
3
\$\begingroup\$

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 <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> {};

With all shapes defined like:

class Sphere : public Shape<Sphere> {...};

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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.