crtp要求编译期类型关系严格钉死:base必须是单参数模板,derived需完整精确命名;继承顺序、函数签名、可见性、定义顺序均不可出错,否则编译失败。

CRTP 不是“写对模板就能跑通”的语法技巧,它要求你把类型关系在编译期就完全钉死——漏传一个模板实参、错放一个定义顺序、拼错一个函数名,编译器立刻报错,且错误位置常偏离真实问题点。
class Derived : public Base 这行必须手写,不能省略或推导
Base 必须是模板,且只接受一个类型参数;派生类继承时,Base<derived></derived> 中的 Derived 必须是当前类的**完整、精确、非别名、非中间基类**的类型名。
- 错误写法:
class Derived : public Base {}→ 编译器报use of class template 'Base' requires template arguments - 错误写法:
class Derived : public Base<void></void>或class Derived : public Base<base>>→static_cast<void>(this)</void>无法调用成员,或触发 incomplete type 错误 - 若派生类本身是模板(如
template<typename t> class Vec3</typename>),继承时必须写全特化名:class Vec3f : public Base<vec3>></vec3>,不能写Base<vec3></vec3> - 头文件中必须先定义
Base模板,再定义Derived类——否则Base<derived></derived>实例化时Derived是 incomplete type
static_cast(this)->func() 要求签名 100% 一致
CRTP 没有重载解析、没有隐式转换、不看 ADL,static_cast<derived>(this)->draw()</derived> 这一行能过,前提是 Derived 真的有一个公有成员函数,名字、参数个数、参数类型(含 const/volatile)、返回类型全部匹配。
- 基类调
draw() const,派生类只提供void draw()→ 编译失败,不是“忽略 const”,是根本找不到可调用函数 - 基类调
compute(int, int),派生类定义double compute(double, double)→ 不会隐式转换,SFINAE 不生效,直接报no member named 'compute' in 'Derived' - 建议在基类接口中加
static_assert校验关键函数存在性,例如:static_assert(std::is_same_v<decltype>().getValue()), int>)</decltype> - 所有被调用的函数必须是
public;私有或保护继承会导致static_cast合法性检查失败
if constexpr 是唯一安全的编译期分支手段
不能在构造函数或普通成员函数里写运行时判断(比如 if (Derived::has_feature)),那既不可见也不安全。分支逻辑必须封装在模板成员函数内,靠 if constexpr 做编译期裁剪。
- 错误做法:在基类构造函数里根据
Derived::value_type分支 →Derived此时尚未完成定义,表达式不可见 - 正确做法:把逻辑写进
template<typename d="Derived"> void process() { if constexpr (has_member_v<d value_type>) { ... } }</d></typename> -
if constexpr的每个分支都必须语法合法,即使未选中;未选中分支里的符号仍需可解析(比如不能引用未定义的嵌套类型) - 配合
std::is_detected_v自定义 trait 判断函数/类型是否存在,比硬写decltype(...)更健壮
Base 和 Base 完全无关,别想用指针统一持有
CRTP 解决的是单类型高性能调度,不是运行时异构集合管理。Base<circle></circle> 和 Base<square></square> 是两个毫无关系的独立类型,没有公共基类,不共享任何指针或引用类型。
-
std::vector<base>>语法非法 —— C++ 没有模板通配符 -
Base*或Base<auto>*</auto>是无效语法,编译器直接拒斥 - 真要存多种派生对象?只能用
std::variant<circle square></circle>+std::visit,或外层包一层虚接口桥接(如让Circle同时继承Base<circle></circle>和Drawable) - 模板实例化膨胀是真实代价:每个
Base<t></t>都生成一份独立代码;高频复用场景建议抽离计算逻辑到inline工具函数,CRTP 基类只做胶水调度
最易被忽略的点是定义顺序和翻译单元可见性:CRTP 所有相关代码(基类模板、派生类定义、调用点)必须在同一翻译单元内可见,不能靠前向声明+cpp 分离实现——否则 Base<derived></derived> 实例化时看不到 Derived 的完整定义,报错信息往往指向奇怪位置。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











