考虑以下两个带有CRTP模式的代码:
template <typename Derived>
struct Base1 {
int baz(typename Derived::value_type) {
return 42;
}
};
struct Foo1 : Base1<Foo1> {
using value_type = int;
};
Run Code Online (Sandbox Code Playgroud)
template <typename Derived>
struct Base2 {
auto baz() {
return typename Derived::value_type {};
}
};
struct Foo2 : Base2<Foo2> {
using value_type = int;
};
Run Code Online (Sandbox Code Playgroud)
第一个编译失败,而第二个编译。我的直觉说,它们应该要么编译要么都不编译。现在,如果我们将autoin 替换Base2为显式类型:
template <typename Derived>
struct Base3 {
typename Derived::value_type baz() {
return typename Derived::value_type {};
}
};
struct Foo3 : Base3<Foo3> {
using value_type = int;
};
Run Code Online (Sandbox Code Playgroud)
它不再编译 ; 但我看不出有什么大不同。这是怎么回事?
注意:这是在C ++-Now 2019的David S.Hollman的闪电演讲《关于奇特重复出现的模板模式的想法》中提出的。
类型Foo1仅在结束时完成};
struct Foo1 : Base1<Foo1> {
// still incomplete
} /* now complete */;
Run Code Online (Sandbox Code Playgroud)
但在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;
}
};
Run Code Online (Sandbox Code Playgroud)
在基类体内,还没有完整的类。您不能在那里使用嵌套类型名。定义类类型时,声明必须全部有效。
在成员函数体内,情况有所不同。
就像这段代码不起作用一样:
struct S {
void f(G) {}
using G = int;
};
Run Code Online (Sandbox Code Playgroud)
但这个还可以:
struct S {
void f() { G g; }
using G = int;
};
Run Code Online (Sandbox Code Playgroud)
在成员函数体内,所有类型都被认为是完整的。
那么...auto如果返回类型推导为您无法访问的类型,为什么它会起作用?
auto返回类型确实很特殊,因为它允许向前声明具有推导返回类型的函数,如下所示:
auto foo();
// later
auto foo() {
return 0;
}
Run Code Online (Sandbox Code Playgroud)
因此,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;
}
Run Code Online (Sandbox Code Playgroud)
只要您不直接使用声明内不完整类型的名称,就可以了。您可以使用间接寻址,例如模板或推导的返回类型,这将使编译器满意。