为何CRTP类体中无法使用派生类类型,但可调用其方法?
为什么CRTP中对不完全类型的检查会延迟到函数调用阶段?
先看这段可正常编译运行的代码:
template <typename CRTP> struct crtp { // using type = typename CRTP::type; // 取消注释会编译失败 void print() const { using type = typename CRTP::type; // 可以正常编译 static_cast<const CRTP&>(*this).print(); } }; #include <iostream> int main() { struct foo : crtp<foo> { using type = int; void print() const { std::cout << "John Cena\n"; }; } f{}; const crtp<foo>& c = f; c.print(); return 0; }
问题场景
当crtp<foo>实例化时,foo还是个不完全类型(因为foo继承crtp<foo>时,自身的定义还没完成)。但调用crtp<foo>的print()方法却能正常运行;可如果取消注释类体中被注释的using type = typename CRTP::type;,会直接触发编译错误:
<source>:4:33: error: no type named 'type' in 'foo' using type = typename CRTP::type; // will not compile ~~~~~~~~~~~~~~~^~~~
核心疑问:为什么C++允许把对CRTP不完全类型的合法性检查延迟到函数调用阶段,而非类对象实例化时?
原因解析
这是C++模板实例化规则的固有特性:
- 类模板的类体成员会在类实例化时立即解析:当
foo继承crtp<foo>时,编译器会立刻实例化crtp<foo>类模板。此时类体中的所有直接成员(比如那个被注释的类型别名type)会被同步处理。但这时foo还没完成定义(属于不完全类型),它的using type = int;还没被编译器处理到,自然找不到对应的成员,直接报错。 - 类模板的成员函数是延迟实例化的:类模板的成员函数(比如这里的
print()),只有当它被实际调用(或被ODR使用)时,才会触发函数体的实例化。这段代码中,c.print()执行时,main里的foo结构体已经完整定义完毕,成为了完全类型,此时再访问foo::type是合法的,编译器能正常找到该类型别名,因此不会报错。
简单来说,类模板的“骨架”(类体成员)会在类实例化时就完成检查,而“血肉”(成员函数体)则延迟到实际使用时才解析——这就给CRTP这种递归继承模式留足了合法运行的空间,让派生类有足够时间完成自身定义,再被基类的成员函数访问。
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

