CRTP与模板模板参数

Sri*_*gas 18 c++ templates crtp template-templates

以下代码无法编译......

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;
}
Run Code Online (Sandbox Code Playgroud)

该行有一个编译错误 - Derived(const T& _t) : Base<T, Derived>(_t) { }

错误C3200''anonymous-namespace':: Derived':模板参数'D'的模板参数无效,需要一个类模板

如果我提供任何其他具有模板参数而不是Derived本身的类,则此方法有效

template<typename T>
struct Other {

};
template<typename T>
struct Derived : Base<T, Other> {
    Derived(const T& _t) : Base<T, Other>(_t) { }
};
Run Code Online (Sandbox Code Playgroud)

Pix*_*ist 17

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

template<typename T>
struct Derived : Base<T, Derived>
{
  Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};
Run Code Online (Sandbox Code Playgroud)

为什么?

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

与普通(非模板)类一样,类模板具有注入类名(第9节).注入的类名可以用作模板名或类型名.当它与template-argument-list一起使用时,作为模板模板参数的模板参数,或作为友元类模板声明的详细说明类型说明符中的最终标识符,它引用类模板本身.

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

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

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

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

+------------------------------+----------+---------+-----------+
|           Method             | MSVS2015 | g++ 5.3 | clang 3.7 |
+------------------------------+----------+---------+-----------+
| Derived                      |    -     |    -    |     -     |
| ::Derived                    |    +     |    +    |     +     |
| Derived<T>::template Derived |    -     |    -    |     +     |
| ::template Derived           |    +     |    -    |     +     |
+------------------------------+----------+---------+-----------+
Run Code Online (Sandbox Code Playgroud)

给名称空间命名时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             |    +     |    +    |     +     |
+---------------------------------+----------+---------+-----------+
Run Code Online (Sandbox Code Playgroud)


T.C*_*.C. 15

在类模板中,inject-class-name(Derived在您的示例中)可以是类型名称和模板名称.该标准指定在用作模板模板参数的参数时应该考虑将模板命名(因此您的代码应该起作用),但遗憾的是一些编译器尚未实现此功能.

一种解决方法是使用限定名称,这样您就不会使用inject-class-name,而是直接命名模板:

template<typename T>
struct Derived : Base<T, Derived> {
    Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};
Run Code Online (Sandbox Code Playgroud)