0

I'm using the Visitor pattern to implement reflection without relying on RTTI. My problem is:

I want to implement a Visitor which can cast different classes DerivedItem1, DerivedItem2, etc. derived from the same BaseItem class, to this BaseItem class.

The base class and one of the derived classes looks like this:

class BaseItem : public AbstractItem
{
    virtual ~BaseItem(){}
    virtual void visit(AbstractVisitor &v)
    {
        v.handle(*this);
    }
}

class DerivedItem1 : public BaseItem
{
    virtual ~DerivedItem(){}
    virtual void visit(AbstractVisitor &v)
    {
        v.handle(*this);
    }
}

The Visitor class:

class BaseVisitor : public AbstractVisitor
{
    virtual ~BaseVisitor(){}
    void handle(BaseItem &item)
    {
        // <-- stuff to do for all classes derived from BaseItem
    }
}

It is not possible to implement the BaseVisitor like this, since DerivedItem::visit(BaseVisitor) does not cast itself to its Base class and BaseVisitor::handle(BaseItem &v) will never get called.

I want to implement the visitor as a template class, taking a base class and all derived classes as template parameters like this:

template <typename BaseT, typename... DerivedT>
class BaseVisitor : public AbstractVisitor
{
public:
    virtual ~BaseVisitor(){}

    // unpacking all DerivedT should happen here
    // DerivedT_X are the packed template arguments ...DerivedT
    void handle(DerivedT_1 &item)
    {
        // <-- cast item to BaseT, do stuff, return BaseT* to caller
    }

    void handle(DerivedT_2 &item)
    {
        // <-- cast item to BaseT, do stuff, return BaseT* to caller
    }
};

Is it possible somehow with C++ to let the compiler generate this member functions on its own ?

  • You might take a `std::tuple` from the variadic template types, and [iterate over this](http://stackoverflow.com/questions/1198260/iterate-over-tuple). – πάντα ῥεῖ Aug 07 '16 at 18:58
  • 1
    IINM, Andrei Alexandrescu has a chapter about this exactly in [Modern C++ Design](https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design). See [this link](http://www.artima.com/cppsource/cooperative_visitor.html) for something similar. – Ami Tavory Aug 07 '16 at 19:04
  • @AmiTavory Ah, very creative. Andrei is really good at that kind of template metaprogramming stuff. – πάντα ῥεῖ Aug 07 '16 at 19:06
  • Unfortunately, [there is no such thing as a virtual template function in C++](http://stackoverflow.com/questions/2354210/can-a-c-class-member-function-template-be-virtual). – Sam Varshavchik Aug 07 '16 at 19:19
  • I believe the OP is asking if he can unpack the argpack across the template definition rather than within a function body. – kfsone Aug 07 '16 at 19:34
  • Can i improve my question somehow to reverse the downvote ? @Sam Varshavchik: I don't want to use virtual template functions, but automatically generate function definitions, all containing the same lines of code – NovicePhantasm Aug 07 '16 at 21:00
  • @NovicePhantasm The downvote is likely the combination of your description being hard to understand and your failure to provide an MVCE which demonstrates your claims. Specifically, the bit about "Derived does not cast itself to Base", which is either badly stated or untrue: http://ideone.com/z2zYj8 – kfsone Aug 07 '16 at 22:04
  • There is no automatic code generation feature in `C++`. If you want to robo-generate code, use an external script that spews out robo-generated code. – Sam Varshavchik Aug 07 '16 at 22:11
  • @kfsone You are right, but only if the visitor's base class doesn't provide handle(Derived&) functions: [link](http://ideone.com/w5DGwI) – NovicePhantasm Aug 08 '16 at 20:29

2 Answers2

0

With CRTP and variadic template, you may do something like:

// The generic visitor interface
template <typename ... Ts>
class IVisitor;

template <> class IVisitor<>
{
public:
    virtual ~IVisitor() = default;
};

template <typename T> class IVisitor<T>
{
public:
    virtual ~IVisitor() = default;
    virtual void visit(const T&) = 0;
};

template <typename T, typename...Ts>
class IVisitor<T, Ts...> : IVisitor<T>, IVisitor<Ts...>
{
public:
    using IVisitor<T>::visit;
    using IVisitor<Ts...>::visit;
    virtual ~IVisitor() = default; 
};

// Helper for the concrete visitor using CRTP
template <typename Derived, typename Base, typename...Ts>
struct CRTPVisitorImpl;

template <typename Derived, typename Base>
struct CRTPVisitorImpl<Derived, Base> : Base {};

template <typename Derived, typename Base, typename T>
struct CRTPVisitorImpl<Derived, Base, T> : virtual Base
{
    using Base::visit;
    void visit(const T& t) override { static_cast<Derived&>(*this).doVisit(t); }    
};

template <typename Derived, typename Base, typename T, typename ... Ts>
struct CRTPVisitorImpl<Derived, Base, T, Ts...> :
    CRTPVisitorImpl<Derived, Base, T>,
    CRTPVisitorImpl<Derived, Base, Ts...>
{
    using CRTPVisitorImpl<Derived, Base, T>::visit;
    using CRTPVisitorImpl<Derived, Base, Ts...>::visit;
};

// The generic Visitor
template <typename Derived, typename Base>
struct CRTPVisitor;

template <typename Derived, typename ... Ts>
struct CRTPVisitor<Derived, IVisitor<Ts...>> :
    CRTPVisitorImpl<Derived, IVisitor<Ts...>, Ts...>
{};

// Helper to write visited
template <typename Derived, typename Base, typename Visitor>
struct Visited : Base
{
    void accept(Visitor& visitor) const override {
        visitor.visit(static_cast<const Derived&>(*this));
    }
};

And usage:

struct ShapeVisitorPrinter : CRTPVisitor<ShapeVisitorPrinter, IShapeVisitor>
{
    template <typename T>
    void doVisit(T&& t) const {
        t.print();
    }
};

each Ivisitor::visit call doVisit with CRTP, so you just have to cover each case via template/overload/base class.

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

You can't unpack the parameter pack across the body of the template definition as you were describing in the question, but you can use CRTP to assemble an class that inherits a hierarchy with templatized specializations for each of the type-parameters you supply:

#include <iostream>

template<class L, class... R> struct X;

template<class L>
struct X<L> { void handle(L& i) { std::cout << i.f() << "\n"; } };

template<class L, class... R>
struct X : public X<L>, public X<R...> { using X<L>::handle; using X<R...>::handle; };

struct A1 {
    int f() { return 1; }
};

struct A2 {
    int f() { return 2; }
};

struct B {
    int f() { return 10; }
};

struct B1 : public B {
    int f() { return 11; }
};

struct B2 : public B1 {
    int f() { return 12; }
};

int main() {
    X<A1, A2> x1;
    A1 a1; A2 a2;
    x1.handle(a1);
    x1.handle(a2);

    X<B, B1, B2> x2;
    B b; B1 b1; B2 b2;
    x2.handle(b);
    x2.handle(b1);
    x2.handle(b2);
}
kfsone
  • 23,617
  • 2
  • 42
  • 74