How to write curiously recurring templates with more than 2 layers of inheritance?
我读过的所有关于奇怪的循环模板模式的材料似乎都是一个继承层,即
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include <iostream> using std::cout; template<typename LowestDerivedClass> class A { public: LowestDerivedClass& get() { return *static_cast<LowestDerivedClass*>(this); } void print() { cout <<"A "; } }; template<typename LowestDerivedClass> class B : public A<LowestDerivedClass> { public: void print() { cout <<"B "; } }; class C : public B<C> { public: void print() { cout <<"C "; } }; int main() { C c; c.get().print(); // B b; // Intentionally bad syntax, // b.get().print(); // to demonstrate what I'm trying to accomplish return 0; } |
我如何重写这段代码以编译而不出错并显示
C
B
使用c.get().print()和b.get().print()?
动机:假设我有三节课,
1 2 3 | class GuiElement { /* ... */ }; class Window : public GuiElement { /* ... */ }; class AlertBox : public Window { /* ... */ }; |
每个类在其构造函数中接受6个左右的参数,其中许多参数是可选的,并且具有合理的默认值。为了避免可选参数的繁琐,最好的解决方案是使用命名参数惯用法。
这个习惯用法的一个基本问题是,参数类的函数必须返回调用它们的对象,但是一些参数被赋予了guiElement,一些参数被赋予了window,还有一些参数被赋予了alertbox。你需要一种方法来写这个:
1 2 3 4 5 | AlertBox box = AlertBoxOptions() .GuiElementParameter(1) .WindowParameter(2) .AlertBoxParameter(3) .create(); |
然而,这可能会失败,因为例如,guielementParameter(int)可能返回guielementOptions&;,而guielementOptions没有windowParameter(int)函数。
这一点以前曾被问过,解决方案似乎是一些奇怪的重复模板模式的味道。我用的特别口味在这里。
但是每次我创建一个新的GUI元素时都要编写大量的代码。我一直在寻找简化它的方法。造成复杂性的一个主要原因是我正在使用crtp来解决命名参数习语问题,但是我有三层而不是两层(guiElement、window和alertbox),而我当前的变通方法是我拥有的类数的四倍。(!)例如,window、windowoptions、windowbuildert和windowbuilder。
这就引出了我的问题,在这里我基本上是在寻找一种更优雅的方法,在继承的长链上使用CRTP,比如guiElement、window和alertbox。
我不完全清楚你希望完成什么,但这是一个接近你所要求的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | template<typename LowestDerivedClass> class A { public: LowestDerivedClass& get() { return *static_cast<LowestDerivedClass*>(this); } void print() { cout <<"A"; } }; template<typename LowestDerivedClass> class Bbase : public A<LowestDerivedClass> { public: void print() { cout <<"B"; this->A<LowestDerivedClass>::print(); } }; class B : public Bbase { }; class C : public Bbase<C> { public: void print() { cout <<"C"; this->Bbase<C>::print(); } }; int main() { C c; c.print(); cout << endl; B b; b.print(); cout << endl; } |
我更改了输出以更好地说明继承。在您的原始代码中,您不能假装
从你的另一个答案来看,(2)是不可能的。如果函数的参数足以推断函数的参数,那么可以不使用函数的模板参数,但是对于类,必须提供一些东西。(1)可以做到,但很尴尬。去掉所有不同的层:
1 2 3 4 5 | template<typename T> struct DefaultTag { typedef T type; }; template<typename Derived = void> class B : public A<Derived> { /* what B should do when inherited from */ }; template<> class B<void> : public A<DefaultTag<B<void> > > { /* what B should do otherwise */ }; |
你必须在每一个层次上做相似的事情。就像我说的,尴尬。你不能简单地说
下面是我已经解决的问题,使用crtp的变体来解决我的动机示例中提出的问题。最好从底部开始向上滚动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | #include"boost/smart_ptr.hpp" using namespace boost; // *** First, the groundwork.... // throw this code in a deep, dark place and never look at it again // // (scroll down for usage example) #define DefineBuilder(TYPE, BASE_TYPE) \ template<typename TargetType, typename ReturnType> \ class TemplatedBuilder<TYPE, TargetType, ReturnType> : public TemplatedBuilder<BASE_TYPE, TargetType, ReturnType> \ { \ protected: \ TemplatedBuilder() {} \ public: \ Returns<ReturnType>::me; \ Builds<TargetType>::options; \ template<typename TargetType> class Builds { public: shared_ptr<TargetType> create() { shared_ptr<TargetType> target(new TargetType(options)); return target; } protected: Builds() {} typename TargetType::Options options; }; template<typename ReturnType> class Returns { protected: Returns() {} ReturnType& me() { return *static_cast<ReturnType*>(this); } }; template<typename Tag, typename TargetType, typename ReturnType> class TemplatedBuilder; template<typename TargetType> class Builder : public TemplatedBuilder<TargetType, TargetType, Builder<TargetType> > {}; struct InheritsNothing {}; template<typename TargetType, typename ReturnType> class TemplatedBuilder<InheritsNothing, TargetType, ReturnType> : public Builds<TargetType>, public Returns<ReturnType> { protected: TemplatedBuilder() {} }; // *** preparation for multiple layer CRTP example *** // // (keep scrolling...) class A { public: struct Options { int a1; char a2; }; protected: A(Options& o) : a1(o.a1), a2(o.a2) {} friend class Builds<A>; int a1; char a2; }; class B : public A { public: struct Options : public A::Options { int b1; char b2; }; protected: B(Options& o) : A(o), b1(o.b1), b2(o.b2) {} friend class Builds; int b1; char b2; }; class C : public B { public: struct Options : public B::Options { int c1; char c2; }; private: C(Options& o) : B(o), c1(o.c1), c2(o.c2) {} friend class Builds<C>; int c1; char c2; }; // *** many layer CRTP example *** // DefineBuilder(A, InheritsNothing) ReturnType& a1(int i) { options.a1 = i; return me(); } ReturnType& a2(char c) { options.a2 = c; return me(); } }; DefineBuilder(B, A) ReturnType& b1(int i) { options.b1 = i; return me(); } ReturnType& b2(char c) { options.b2 = c; return me(); } }; DefineBuilder(C, B) ReturnType& c1(int i) { options.c1 = i; return me(); } ReturnType& c2(char c) { options.c2 = c; return me(); } }; // note that I could go on forever like this, // i.e. with DefineBuilder(D, C), and so on. // // ReturnType will always be the first parameter passed to DefineBuilder. // ie, in 'DefineBuilder(C, B)', ReturnType will be C. // *** and finally, using many layer CRTP builders to construct objects ***/ int main() { shared_ptr<A> a = Builder<A>().a1(1).a2('x').create(); shared_ptr b = Builder().a1(1).b1(2).a2('x').b2('y').create(); shared_ptr c = Builder<C>().c2('z').a1(1).b1(2).a2('x').c1(3).b2('y').create(); // (note: any order works) return 0; }; |
我认为实现一些通用机制是不可能的。每次继承基类时,都必须显式地指定精确的模板参数,不管中间放置了多少个间接级别(根据您的答案判断:现在有两个级别:您不直接将C传递到基部,而是将C包裹在标记结构中,它看起来像一条咬住自己尾巴的蛇)
也许,您的任务最好使用类型擦除,而不是奇怪的重复模板模式。可能,这会有用的