具有模板模板参数的CRTP

CRTP with Template Template Arguments

本文关键字:CRTP 参数      更新时间:2023-10-16

以下代码没有编译。。。

namespace {
    template<typename T, template<typename> class D>
    struct Base {
        Base(const T& _t) : t(_t) { }
        T t;
    };
    template<typename T>
    struct Derived : Base<T, Derived> {
        Derived(const T& _t) : Base<T, Derived>(_t) { }
    };
}
int main(int argc, char* argv[]) {
    Derived<int> d(1);
    return 0;
}

线路Derived(const T& _t) : Base<T, Derived>(_t) { } 上存在编译错误

错误C3200"匿名命名空间"::派生的:无效模板模板参数"D"的参数,应为类模板

如果我提供任何其他具有模板参数而不是派生自身的类,这就有效了

template<typename T>
struct Other {
};
template<typename T>
struct Derived : Base<T, Other> {
    Derived(const T& _t) : Base<T, Other>(_t) { }
};

Tl;dr:解决这个问题的最便携、最不广泛的方法似乎是在您的示例中使用限定名::Derived

template<typename T>
struct Derived : Base<T, Derived>
{
  Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};

为什么

问题是编译器不符合C++11。

与普通(非模板)类一样,类模板有一个注入的类名(第9条)注入的类名可以用作模板名或类型名当它与模板参数列表一起使用时,用作模板模板参数的模板参数,或者用作友类模板声明的详细类型说明符中的最终标识符时,它指的是类模板本身

因此,您的代码应该进行编译,但不幸的是,我测试过的所有编译器(clang 3.7、Visual Studio 2015和g++5.3)都拒绝这样做

Derived定义中,您应该能够以各种方式表示模板:

  • 直接使用注入的名称Derived
  • 类模板的合格名称::Derived
  • 使用注入的类名,将其指定为模板名Derived<T>::template Derived
  • 使用限定名称,再次将其指定为模板名称::template Derived

关于这四个选项,这些编译器的编译状态如下(使用匿名名称空间;其中+=成功编译):

+------------------------------+----------+---------+-----------+
|           Method             | MSVS2015 | g++ 5.3 | clang 3.7 |
+------------------------------+----------+---------+-----------+
| Derived                      |    -     |    -    |     -     |
| ::Derived                    |    +     |    +    |     +     |
| Derived<T>::template Derived |    -     |    -    |     +     |
| ::template Derived           |    +     |    -    |     +     |
+------------------------------+----------+---------+-----------+

当将名称空间命名为X时,图片会发生一些变化(即g++现在接受X::template Derived,而拒绝::template Derived):

+---------------------------------+----------+---------+-----------+
|            Method               | MSVS2015 | g++ 5.3 | clang 3.7 |
+---------------------------------+----------+---------+-----------+
| Derived                         |    -     |    -    |     -     |
| X::Derived                      |    +     |    +    |     +     |
| X::Derived<T>::template Derived |    -     |    -    |     +     |
| X::template Derived             |    +     |    +    |     +     |
+---------------------------------+----------+---------+-----------+

在类模板中,注入的类名(示例中为Derived)既可以是类型名,也可以是模板名。该标准规定,当用作模板模板参数的参数时,应考虑命名模板(因此您的代码应该可以工作),但不幸的是,一些编译器尚未实现这一点。

一种解决方法是使用限定名称,这样就不用注入的类名,而是直接命名模板:

template<typename T>
struct Derived : Base<T, Derived> {
    Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};