【问题标题】:CRTP with Template Template Arguments带有模板模板参数的 CRTP
【发布时间】:2016-04-16 19:48:58
【问题描述】:

以下代码无法编译...

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&amp; _t) : Base&lt;T, Derived&gt;(_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) { }
};

【问题讨论】:

标签: c++ templates crtp template-templates


【解决方案1】:

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)都拒绝编译。

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

  • 直接使用注入名称Derived
  • 类模板的限定名称::Derived
  • 使用注入的类名,指定为模板名Derived&lt;T&gt;::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             |    +     |    +    |     +     |
+---------------------------------+----------+---------+-----------+

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • “一些编译器”是哪个?
    • @PiotrSkotnicki 你认为它应该始终是一个 template-name 并且不能用作 type-name
    • @PiotrSkotnicki:正确。 The point of declaration for an injected-class-name (Clause 9) is immediately following the opening brace of the class definition.
    • @PiotrSkotnicki 哦,对,在基本说明符中。你是对的 - 每个人都认为一个是 template-name。固定。
    • 使用通过Derived::template Derived 注入的名称表示类模板使用clang 3.7 编译,但被MSVC 2015 和g++5.3 拒绝。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-08
    • 1970-01-01
    • 2021-03-30
    • 2011-08-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多