使用带有附加类型参数的奇怪重复模板模式 (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<A, double, int>
——尽管这种参数顺序不匹配并不那么明显。如果我可以在 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<TDerived>::IntType
时出现 invalid use of incomplete type
错误。在此处的示例中,给出了所需的专业化BaseTraits<Derived>
,但在实际代码中,这不是在其他地方(就像在我的代码中一样)吗?所以只有前向声明可用,所以类型通常是不完整的?我该如何解决这个问题?在我的代码中将特化与基声明放在一起是没有意义的,因为它在逻辑上是派生类的一部分。
啊,没关系,确实只是有一个问题,错误的模板参数被传递给 BaseTraits,所以它确实没有尝试使用我在其他地方定义的专业化。一旦模板参数正确匹配,就可以正常工作:)。【参考方案2】:
@James 的回答显然是正确的,但如果用户没有提供正确的 typedef,您仍然可能会遇到一些问题。
可以使用编译时检查工具“断言”所使用的类型是正确。根据您使用的 C++ 版本,您可能必须使用 Boost。
在 C++0x 中,这是通过组合完成的:
static_assert
:一种新的编译时检查工具,可以让你指定消息
type_traits
标头,它提供了一些谓词,例如 std::is_integral
或 std::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<float, int>
而不是Derived<int, float>
的错误很难被发现。
好吧,您可以包含上一篇文章中建议的静态断言 Matthieu M.。如果将名称 T1
替换为例如FloatType_
和 T2
,例如IntType_
(然后是 typedef IntType_ IntType;
和 typedef FloatType_ FloatType;
)。或者,您可以使用“静态 if 语句”来确定 T1 是否确实是整数类型,如果是,则 typedef T1 到 IntType,如果不是,则 typedef T2 到 IntType。以上是关于使用带有附加类型参数的奇怪重复模板模式 (CRTP)的主要内容,如果未能解决你的问题,请参考以下文章
使用奇怪的重复模板模式 (CRTP) 在抽象基类中实现赋值运算符