可变参数模板类:是不是可以为每个可变参数模板参数实现一个唯一的成员函数?
Posted
技术标签:
【中文标题】可变参数模板类:是不是可以为每个可变参数模板参数实现一个唯一的成员函数?【英文标题】:Variadic template class: Is it possible to implement one unique member function per variadic template argument?可变参数模板类:是否可以为每个可变参数模板参数实现一个唯一的成员函数? 【发布时间】:2016-08-07 18:50:50 【问题描述】:我正在使用访问者模式来实现反射,而不依赖于 RTTI。 我的问题是:
我想实现一个访问者,它可以将派生自同一个 BaseItem 类的不同类 DerivedItem1、DerivedItem2 等转换为这个 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,
因为DerivedItem::visit(BaseVisitor)
不会将自己转换为它的基类
并且BaseVisitor::handle(BaseItem &v)
永远不会被调用。
我想将访问者实现为模板类,将基类和所有派生类作为模板参数,如下所示:
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++ 让编译器自行生成此成员函数?
【问题讨论】:
您可以从可变参数模板类型中获取std::tuple
和iterate over this。
IINM,Andrei Alexandrescu 在Modern C++ Design 中有一个关于此的章节。请参阅this link 了解类似情况。
@AmiTavory 啊,很有创意。 Andrei 真的很擅长那种模板元编程的东西。
很遗憾,there is no such thing as a virtual template function in C++.
我相信 OP 是在询问他是否可以在模板定义中而不是在函数体中解压缩 argpack。
【参考方案1】:
您不能像在问题中描述的那样在模板定义的主体中解压缩参数包,但您可以使用 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);
【讨论】:
【参考方案2】:使用 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
,因此您只需通过模板/重载/基类覆盖每种情况。
Demo
【讨论】:
以上是关于可变参数模板类:是不是可以为每个可变参数模板参数实现一个唯一的成员函数?的主要内容,如果未能解决你的问题,请参考以下文章