可变模板类:每个可变模板参数可以实现一个唯一的成员函数吗

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

本文关键字:唯一 一个 函数 成员 参数 实现      更新时间:2023-10-16

我使用Visitor模式来实现反射,而不依赖RTTI。我的问题是:

我想实现一个Visitor,它可以将从同一BaseItem类派生的不同类DerivedItem1、DerivedItems2等强制转换为此BaseItem类

基类和其中一个派生类如下所示:

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

访问者类别:

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

这样实现BaseVisitor是不可能的,因为CCD_ 1不将自身强制转换为其基类并且CCD_ 2将永远不会被调用。

我想将访问者实现为一个模板类,将基类和所有派生类作为模板参数,如下所示:

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

是否可以用C++让编译器自己生成这个成员函数

您不能像在问题中描述的那样,在模板定义的整个主体中解压缩参数包,但您可以使用CRTP来组装一个类,该类继承了一个层次结构,并为您提供的每个类型参数进行了模板化的专门化:

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

使用CRTP和可变模板,您可以执行以下操作:

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

用途:

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

每个Ivisitor::visit都用CRTP调用doVisit,所以您只需要通过template/overload/base-class覆盖每个情况。

演示