typedef上与CRTP相关的编译器错误

时间:2011-08-29 11:51:06

标签: c++ compiler-construction typedef crtp

对于CRTP,我有一个相当简单的问题,但我似乎无法找到答案。也许,因为它如此简单,没有人想过要问它。我是这个概念的新手,所以,请不要笑得太厉害;)。

这是代码(它尝试做类似于STL容器的尝试):

template< typename tT >
struct TBase
{
  typedef tT T;
};

template< typename tTBase >
struct TTraitsBase
{
  typedef typename tTBase::T T;
};

template< typename tTHelpee, typename tTTraits >
struct THelper
{

  typedef typename tTTraits::T T;
  typedef typename tTHelpee::T Th; /* This generates a compiler error:
                                      'T' not being a member of TDerived<tT> */
  T Foo( void )
  {
   return static_cast< tTHelpee* > ( this )->mVal;
  }
};

template< typename tT >
struct TDerived : TBase< tT > , THelper< TDerived< tT > , TTraitsBase< TBase< tT > > >
{
 using TBase< tT >::T;
  T mVal;
};

int main()
{
 TDerived< int >::T lTmp = -1;
 TDerived< int > lObj;

 lObj.mVal = -1;
 std::cout << lObj.Foo() << std::endl;

 return 0;
}

如果我评论有问题的typedef typename _THelpee::T Th;,那么一切都会编译。令我困惑的是:如果编译器不喜欢typedef typename _THelpee::T Th;,为什么它会通过static_cast< _THelpee* > ( this )->mVal?我假设,它与在实例化THelper时无法实例化TDerived有关,但没有明确的理解。有人可以给出一个简短的解释和/或一些关于这里发生的事情的参考吗?谢谢。

编辑:删除'_T'前缀。

3 个答案:

答案 0 :(得分:2)

发布的代码有几个问题。既然,我无法说出意图,我会列出我发现的错误。

(1)使用不完整类型

template< typename _T >
struct TDerived : TBase< _T > , THelper< TDerived< _T > , TTraitsBase< TBase< _T > > >
                                         ^^^^^^^^^^^^^^ it's incomplete

IMO在定义TDerived<_T>的正文时,不应该将参数作为参数使用。

(2)不正确的类型定义。

using TBase< _T >::T;

应该是,

typedef typename TBase< _T >::T T;

答案 1 :(得分:2)

隐式实例化类不会实例化其成员函数定义。只有在使用时才会实例化类模板的每个成员(除非您使用显式实例化)。

您隐式实例化的第一件事是TDerived<int>,位于main的第一行。为了实例化,编译器查找TDerived模板,发现有几个依赖的基类,所以尝试实例化它们。 TBase<int>实例化没有问题。但是在尝试实例化THelper< TDerived<int>, TTraitsBase< TBase<int> > >时,尝试获取成员TDerived<int>::T,但TDerived<int>仍然是不完整的类型。

实例化THelper< ... >还注意到存在成员函数int Foo(),但不评估其定义的语义。

当您从lObj.Foo()致电main时,int THelper< ... >::Foo()实例化TDerived<int>,{{1}}是完整类型,因此没有任何问题。

答案 2 :(得分:1)

我会说你的CRTP中有一个循环依赖(就其本质而言),但这意味着基本模板类不允许使用任何一个它的参数类,因为那个参数类还没有定义 - 它在实例化Base<T>时仍然是一个不完整的类型。

所以,这没关系:

template <typename T> struct Base
{
  T * impl;  // incomplete type is OK
};

class Foo : public Base<Foo> { /* ... */ };

但这不是:

template <typename T> struct Base
{
  T x; // need to know T, but T depends on Base<T>!
};