关于c ++:为什么我的好奇重复模板模式(CRTP)不能引用派生类的typedef?

Why can't my Curiously Recurring Template Pattern (CRTP) refer to the derived class's typedefs?

本问题已经有最佳答案,请猛点这里访问。

当使用奇怪的循环模板模式时,只有在尝试从基类引用属于派生类的typedef时,我才能引用它们;gcc抱怨no type named 'myType' in class Derived<...>。这似乎与使用typedef、模板和奇怪的重复关系的其他可能方式不一致。

考虑:

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继承仍然存在于一个奇怪的重复出现的模板关系中;我们通过three声明中的派生类引用基的typedef。

从(4)可以看出,派生类的成员变量可以很容易地从基类访问。

从(5)可以看到,我们可以访问模板参数上定义的typedef(当我们使用与继承无关的类型声明five时,这是有效的)。

然而,当我们在(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;

换句话说,A::my_typeD::inner_type之间的一致性是由traits::inner_type来保证的,traits::inner_type的定义是独立的。