使用带有附加类型参数的奇怪重复模板模式 (CRTP)

Posted

技术标签:

【中文标题】使用带有附加类型参数的奇怪重复模板模式 (CRTP)【英文标题】:Use Curiously Recurring Template Pattern (CRTP) with additional type parameters 【发布时间】:2011-08-06 12:29:09 【问题描述】:

我尝试使用 Curiously Recurring Template Pattern (CRTP) 并提供额外的类型参数:

template <typename Subclass, typename Int, typename Float>
class Base 
    Int *i;
    Float *f;
;
...

class A : public Base<A, double, int> 
;

这可能是一个错误,更合适的超类应该是Base&lt;A, double, int&gt;——尽管这种参数顺序不匹配并不那么明显。如果我可以在 typedef 中使用 name 参数的含义,这个 bug 会更容易看出:

template <typename Subclass>
class Base 
    typename Subclass::Int_t *i;  // error: invalid use of incomplete type ‘class A’
    typename Subclass::Float_t *f;
;

class A : public Base<A> 
    typedef double Int_t;         // error: forward declaration of ‘class A’
    typedef int Double_t;
;

但是,这在 gcc 4.4 上无法编译,报告的错误以上面的 cmets 给出——我认为原因是在创建 A 之前,它需要实例化 Base 模板,但这反过来又需要知道 A .

在使用 CRTP 时,有没有一种很好的方法可以传入“命名”模板参数?

【问题讨论】:

只是为了确认,您认为是对的 :) 这确实是因为您的提议在尝试实例化 A 时会导致无限递归。 C++ static polymorphism (CRTP) and using typedefs from derived classes的可能重复 【参考方案1】:

您可以使用特征类:

// Must be specialized for any type used as TDerived in Base<TDerived>.
// Each specialization must provide an IntType typedef and a FloatType typedef.
template <typename TDerived>
struct BaseTraits;

template <typename TDerived>
struct Base 

    typename BaseTraits<TDerived>::IntType *i;
    typename BaseTraits<TDerived>::FloatType *f;
;

struct Derived;

template <>
struct BaseTraits<Derived> 

    typedef int IntType;
    typedef float FloatType;
;

struct Derived : Base<Derived> 

;

【讨论】:

不错!感谢您的回答。 我遇到了一些问题;尝试在 Base 模板结构中访问 typename BaseTraits&lt;TDerived&gt;::IntType 时出现 invalid use of incomplete type 错误。在此处的示例中,给出了所需的专业化BaseTraits&lt;Derived&gt;,但在实际代码中,这不是在其他地方(就像在我的代码中一样)吗?所以只有前向声明可用,所以类型通常是不完整的?我该如何解决这个问题?在我的代码中将特化与基声明放在一起是没有意义的,因为它在逻辑上是派生类的一部分。 啊,没关系,确实只是有一个问题,错误的模板参数被传递给 BaseTraits,所以它确实没有尝试使用我在其他地方定义的专业化。一旦模板参数正确匹配,就可以正常工作:)。【参考方案2】:

@James 的回答显然是正确的,但如果用户没有提供正确的 typedef,您仍然可能会遇到一些问题。

可以使用编译时检查工具“断言”所使用的类型是正确。根据您使用的 C++ 版本,您可能必须使用 Boost。

在 C++0x 中,这是通过组合完成的:

static_assert:一种新的编译时检查工具,可以让你指定消息 type_traits 标头,它提供了一些谓词,例如 std::is_integralstd::is_floating_point

例子:

template <typename TDerived>
struct Base

  typedef typename BaseTraits<TDerived>::IntType IntType;
  typedef typename BaseTraits<TDerived>::FloatType FloatType;

  static_assert(std::is_integral<IntType>::value,
    "BaseTraits<TDerived>::IntType should have been an integral type");
  static_assert(std::is_floating_point<FloatType>::value,
    "BaseTraits<TDerived>::FloatType should have been a floating point type");

;

这与运行时世界中典型的防御性编程习语非常相似。

【讨论】:

【参考方案3】:

实际上你甚至不需要特质类。以下也有效:

template 
<
   typename T1, 
   typename T2, 
   template <typename, typename> class Derived_
>
class Base

public:
   typedef T1 TypeOne;
   typedef T2 TypeTwo;
   typedef Derived_<T1, T2> DerivedType;
;

template <typename T1, typename T2>
class Derived : public Base<T1, T2, Derived>

public:
   typedef Base<T1, T2, Derived> BaseType;
   // or use T1 and T2 as you need it
;

int main()

   typedef Derived<int, float> MyDerivedType;
   MyDerivedType Test;

   return 0;

【讨论】:

嗯,这没有预期的解决方案那么简单——使用Derived&lt;float, int&gt; 而不是Derived&lt;int, float&gt; 的错误很难被发现。 好吧,您可以包含上一篇文章中建议的静态断言 Matthieu M.。如果将名称 T1 替换为例如FloatType_T2,例如IntType_(然后是 typedef IntType_ IntType;typedef FloatType_ FloatType;)。或者,您可以使用“静态 if 语句”来确定 T1 是否确实是整数类型,如果是,则 typedef T1 到 IntType,如果不是,则 typedef T2 到 IntType。

以上是关于使用带有附加类型参数的奇怪重复模板模式 (CRTP)的主要内容,如果未能解决你的问题,请参考以下文章

使用奇怪的重复模板模式 (CRTP) 在抽象基类中实现赋值运算符

C++ 静态多态性 (CRTP) 和使用派生类的 typedef

c++派生类的类型列表

CRTP 特征仅适用于模板派生类

带有非类型参数的奇怪模板实例化错误

浅谈 CRTP:奇异递归模板模式