【问题标题】:Why can't my Curiously Recurring Template Pattern (CRTP) refer to the derived class's typedefs? [duplicate]为什么我的奇怪重复模板模式 (CRTP) 不能引用派生类的 typedef? [复制]
【发布时间】:2013-04-19 06:35:18
【问题描述】:

当使用curiously recurring template pattern 时,如果我尝试从基类引用它们,我将无法引用属于派生类的类型定义只有; gcc 抱怨no type named 'myType' in class Derived<...>。这似乎与使用 typedef、模板和奇怪的重复关系可能实现的不一致。

考虑:

/* 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 突然变得不可访问,尽管它看起来与派生类中的任何成员变量一样定义良好。

这是为什么?

【问题讨论】:

  • 不要太担心“奇怪地反复出现”这个词组。如果你彻底分析它,它并不是真的“反复出现”。
  • 它在哪个编译器上失败了什么消息?
  • @PeterWood:在这个例子中,它以error: no type named 'c_t' in 'class tCuriousTypeDerived&lt;double&gt;' 失败。

标签: c++ templates gcc typedef crtp


【解决方案1】:

这是“循环依赖”,或“不完整类型”的改变自我。

模板“元编程”是“编程类型”,但它需要知道一定程度的语义才能正确实例化类型。

考虑这个类比

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 是

class A
{
   B m;
};

除了指针没有其他解决方案,因为递归将是无限的。 (A 应该包含自己,而不是引用另一个副本)

现在,以同样的比喻,让我们编写“类型”:

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 定义为 ...

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 循环之外使用一些“特征类”:

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

我们终于可以声明了

typedef D<traits> D_Inst;

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

【讨论】:

  • 我认为这个类比不适用。如果 Base 使用派生的 typedef 返回值声明成员函数,而不基于 Derived 命名成员变量,则仍然会引发错误。事实上,如果我只在 Base 中创建一个 typedef 并将 Derived 中的 typedef 别名化,并且不再引用它,错误仍然存​​在!另外,我认为类型解析必须在实际类实例化甚至可以开始之前发生。类型定义中没有循环,所以我仍然不明白为什么这不起作用。
  • 另外,当我调用static_cast&lt;Derived*&gt;(this)-&gt;foo() 时,编译器解析和检查foo() 的返回类型没有问题,这本身可能取决于Base 的属性。我不明白为什么解析 typedef 的类型更难。
  • @trbabb:这是“类比”,而不是“身份”。变量声明只是一个例子。问题的根源在于DA&lt;D&gt; 用于形成D 时被认为是incolmplete 因此您正在使用其中的不完整类型来形成一种类型。你使用它的方式无关紧要(我只是做了一个例子,但它不是唯一可能的)。阻碍语义分析的是不完整性
  • @trbabb: ...关于调用函数,那是完全不同的野兽:你只是指向一个符号表的条目,其内容(调用地址)稍后将由链接器定义。
  • 在我的示例中检查 foo() 的返回类型是否与我可能分配给它的任何内容兼容
猜你喜欢
  • 2019-11-18
  • 1970-01-01
相关资源
最近更新 更多