Search code examples
c++variadic-templatestemplate-classes

Variadic template class: Is it possible to implement one unique member function per variadic template argument?


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 ?


Solution

  • 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);
    }