Why can't my Curiously Recurring Template Pattern (CRTP) refer to the derived class's typedefs?
当使用奇怪的循环模板模式时,只有在尝试从基类引用属于派生类的typedef时,我才能引用它们;gcc抱怨
考虑:
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 | /* crtp.cpp */ #include <iostream> using namespace std; // case 1. simple. class Base { public: typedef int a_t; a_t foo; }; class Derived : public Base { a_t bar; }; // case 2. template. template<typename T> class tBase { public: typedef T b_t; T foo; }; template <typename T> class tDerived : public tBase<T> { typename tBase<T>::b_t bar; }; // case 3. curiously recurring. template <typename T, typename D> class tCuriousBase { public: typedef T c_t; c_t foo; }; template <typename T> class tCuriousDerived : public tCuriousBase<T,tCuriousDerived<T> > { typename tCuriousBase<T,tCuriousDerived<T> >::c_t bar; }; // case 4. curiously recurring with member reference. template <typename T, typename D> class tCuriousMemberBase { public: T foo; T get() { return static_cast<D*>(this)->bar; } }; template <typename T> class tCuriousMemberDerived : public tCuriousMemberBase<T, tCuriousMemberDerived<T> > { public: T bar; tCuriousMemberDerived(T val) : bar(val) {} }; // case 5. curiously recurring with typedef reference. template <typename T, typename D> class tCuriousTypeBase { public: typedef T d_t; d_t foo; typename D::c_t baz; }; template <typename T> class tCuriousTypeDerived : public tCuriousTypeBase<T, tCuriousTypeDerived<T> > { public: typedef T c_t; typename tCuriousTypeBase<T,tCuriousTypeDerived<T> >::d_t bar; }; // entry point int main(int argc, char **argv) { Derived::a_t one = 1; tDerived<double>::b_t two = 2; tCuriousDerived<double>::c_t three = 3; double four = tCuriousMemberDerived<double>(4).get(); tCuriousTypeBase<double, tCuriousDerived<double> >::d_t five = 5; // tCuriousTypeDerived<double>::d_t six = 6; /* FAILS */ cout << one << endl; cout << two << endl; cout << three << endl; cout << four << endl; cout << five << endl; // cout << six << endl; } |
从(1)可以看出,typedef实际上是从基继承到派生的;基类中声明的typedef可以通过派生类访问。
从(2)可以看出,如果两个类都是模板,那么这仍然是正确的。
从(3)中,我们可以看到这个typedef继承仍然存在于一个奇怪的重复出现的模板关系中;我们通过
从(4)可以看出,派生类的成员变量可以很容易地从基类访问。
从(5)可以看到,我们可以访问模板参数上定义的typedef(当我们使用与继承无关的类型声明
然而,当我们在(6)中将模板参数设置为派生类时,typedef会突然变得不可访问,即使它看起来像派生类中的任何成员变量一样定义良好。
为什么会这样?
型
这就是"循环依赖",或者"不完整类型"的另一个自我。
模板"元编程"是"编程类型",但要正确实例化类型,需要知道一定级别的语义。
考虑这个类比:
1 2 3 4 5 6 7 8 9 10 11 12 | class A; //forwarded class B { A* pa; //good: we know how wide a pointer is, no matter if we don't know yet anything about A. A a; // bad: we don't know how much space it requires }; class A { float m; }; // now we know, but it's too late |
这可以通过将a放在b之前来解决,但如果a是
1 2 3 4 | class A { B m; }; |
号
除了指针,这里没有其他解决方案,因为递归是无限的。(A应包含其本身,而不是另一份副本)
现在,用同样的类比,让我们对"类型"进行编程:
1 2 3 4 5 6 | template<class D> class A { typedef typename D::inner_type my_type; //required D to be known when A<D> is instantiated... my_type m; // ... otherwise we cannot know how wide A<D> will be. }; |
这个声明本身并不坏,直到我们开始将d定义为…
1 2 3 4 5 6 7 | class D: //here we only know D exist public A<D> //but A size depende on D definition... { .... typedef long double; inner_type .... }; // ....we know only starting from here |
。
所以,基本上,我们还不知道A在当时有多宽,需要用它来创建D。
打破这种"循环性"的一种方法是在CRT循环之外使用一些"特征类":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | struct traits { typedef long double inner_type; .... }; template<class D, class Traits> class A { // this is easy: Traits does not depend itself on A typedef typename Traits::inner_type my_type; .... }; template<class Traits> class D: public A<D, Traits> { typedef typename Traits::inner_type inner_type; }; |
我们终于可以宣布
1 | typedef D<traits> D_Inst; |
。
换句话说,