【问题标题】:Why does only one of these CRTP patterns compile?为什么这些 CRTP 模式中只有一种可以编译?
【发布时间】:2019-08-27 15:08:19
【问题描述】:

考虑以下两段带有CRTP 模式的代码:

template <typename Derived>
struct Base1 {
    int baz(typename Derived::value_type) { 
        return 42; 
    }
};

struct Foo1 : Base1<Foo1> {
    using value_type = int;
};
template <typename Derived>
struct Base2 {
    auto baz() {
        return typename Derived::value_type {};
    }
};

struct Foo2 : Base2<Foo2> {
    using value_type = int;
};

第一个fails to compile,第二个compiles。我的直觉说他们应该要么编译要么都不编译。现在,如果我们将Base2 中的auto 替换为显式类型:

template <typename Derived>
struct Base3 {
    typename Derived::value_type baz() {
        return typename Derived::value_type {};
    }
};

struct Foo3 : Base3<Foo3> {
    using value_type = int;
};

no longer compiles;但我真的不明白这里有什么大区别。怎么回事?


注意:这出现在 David S. Hollman 的闪电演讲中,Thoughts on Curiously Recurring Template Pattern,在 C++-Now 2019 中。

【问题讨论】:

  • @PicaudVincent:谢谢,看起来很有趣。太糟糕了,该博客上的字体/背景对比很差。

标签: c++ auto crtp


【解决方案1】:

Foo1 类型仅在}; 的末尾是完整的

struct Foo1 : Base1<Foo1> {
    // still incomplete
} /* now complete */;

但在 Foo1 开始被定义之前,它必须先实例化基类才能完成基类。

template <typename Derived>
struct Base1 {
    // Here, no type are complete yet

    // function declaration using a member of incomplete type
    int baz(typename Derived::value_type) { 
        return 42; 
    }
};

在基类体内,还没有一个类是完整的。你不能在那里使用嵌套的类型名。定义类类型时,声明必须全部有效。

在成员函数体内,情况不同。

就像这段代码不起作用:

struct S {
    void f(G) {}
    using G = int;
};

不过这个没问题:

struct S {
    void f() { G g; }
    using G = int;
};

在成员函数体内,所有类型都被认为是完整的。

那么...如果auto 返回类型推断为您无法访问的类型,为什么它会起作用?

auto 返回类型确实很特殊,因为它允许前向声明具有推导返回类型的函数,如下所示:

auto foo();

// later

auto foo() {
    return 0;
}

因此,auto 的扣除可用于延迟声明中不完整的类型的使用。

如果auto 是即时推导出来的,则函数主体中的类型不会像规范所暗示的那样完整,因为它必须在定义类型时实例化函数主体。


至于参数类型,也是函数声明的一部分,所以派生类还是不完整的。

虽然不能使用不完全类型,但可以检查推导的参数类型是否真的是typename Derived::value_type

即使实例化的函数收到typename Derived::value_type(当使用正确的参数集调用时),它也只在实例化点定义。到那时,类型就完成了。

有一些类似于自动返回类型的东西,但对于参数,这意味着一个模板:

template<typename T>
int baz(T) {
    static_assert(std::is_same_v<typename Derived::value_type, T>) 
    return 42; 
}

只要您不直接使用声明中不完整类型的名称,就可以了。您可以使用诸如模板或推导返回类型之类的间接方法,这将使编译器满意。

【讨论】:

  • 我认为这不能回答我的问题。 Foo2::baz() 在其声明中有效地包含 Derived::value_type - 通过使用 auto。那么为什么它比明确使用Derived::value_type 时更完整呢?
  • 好吧,如果将返回类型确定延迟到实例化是可以的,那为什么对于参数类型就不行了?
  • 您的实际答案是以“自动返回类型确实很特殊”开头的单段。
  • @einpoklum 你的问题问为什么一个编译而另一个不编译,所以我必须首先解释类型在哪里以及为什么是完整或不完整的。我清理了答案,现在应该更好了。
【解决方案2】:

tl;dr:因为对 auto 返回类型的特殊考虑。

(@GuillaumeRacicot 回答的缩短版)

当定义模板类时,编译器需要在成员函数的签名中声明所有类型(指针或引用除外)。 Derived::value_type 未知,因此 Base1Base3 不编译。

但是 auto 返回类型有一个特殊的例外:就好像您在前向声明 auto 返回类型,并且可以在实际实例化成员时实际定义。这就是Base2 确实编译的原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-21
    • 1970-01-01
    • 1970-01-01
    • 2021-07-19
    • 1970-01-01
    相关资源
    最近更新 更多