CRTP是必须显式声明派生类类型、严格匹配签名、放弃运行时统一容器能力的编译期契约;Base是模板名而非类型,class Derived : public Base因未提供模板参数而编译失败,只有Base<Derived>才能触发实例化并使static_cast(this)有效。

CRTP 不是“去掉虚函数就能用”的替代品,而是必须显式声明派生类类型、严格匹配签名、放弃运行时统一容器能力的一套编译期契约——写错一个模板参数或漏掉一个 const,编译直接失败。
为什么 class Derived : public Base 会编译失败
因为 Base 是模板名,不是类型;编译器根本不知道你要实例化哪个特化版本。只有 Base<derived></derived> 才能触发模板实例化,让基类内部的 static_cast<derived>(this)</derived> 有据可依。
- 写成
class Derived : public Base<void></void>可能通过语法检查,但static_cast<void>(this)</void>无法调用任何成员,后续调用直接报错 - 写成
class Derived : public Base<base>>会导致无限递归实例化,编译器通常在模板深度超限后中止 - Clang/GCC 在
-Wall下会对未特化的模板名使用给出明确提示,比如 “use of class template ‘Base’ requires template arguments”
static_cast<derived>(this)->func()</derived> 要求什么
这不是松散的鸭子类型,而是硬性接口对齐:函数名、参数个数、每个参数类型(含 const 限定)、返回类型,全部必须字面一致。
- 基类写
void draw() const,派生类只写void draw()→ 编译失败,错误常指向基类调用行,而非派生类定义处 - 基类调用
compute(int, int),派生类定义double compute(long, long)→ 不会隐式转换,直接 “no member named ‘compute’” - 建议在基类关键函数里加
static_assert校验,例如:static_assert(std::is_same_v<decltype>().getValue()), int>)</decltype>
为什么不能把 Base<circle></circle> 和 Base<square></square> 放进同一个 std::vector
它们是完全不相关的类型,就像 int* 和 char* 不能混存一样。CRTP 没有“父类指针”这一层抽象,Base<circle>*</circle> 和 Base<square>*</square> 之间没有继承关系,也不能隐式转换。
立即学习“C++免费学习笔记(深入)”;
- 想统一持有多个 CRTP 对象?只能用
std::variant<circle square></circle>+std::visit,或者包装进std::any - 试图定义
template<typename t> using BasePtr = Base<t>*;</t></typename>来“泛化指针” → 语法非法,C++ 不支持这种模板别名用于多态容器 - 若真需要运行时调度,得在外层加策略枚举 +
switch,或用if constexpr在模板函数内分支,而不是依赖基类本身
容易被忽略的 ABI 和模块边界问题
CRTP 类型不具备跨编译单元的多态兼容性:你在 A.cpp 里定义的 Base<concrete></concrete> 和 B.cpp 里同名的 Base<concrete></concrete>,即使代码一字不差,在链接时也可能被视为不同类型——尤其当模板定义不在头文件中、或 ODR 违反时。
- 所有 CRTP 基类模板定义必须完整放在头文件里,不能分离声明与实现
- 禁用私有/保护继承,
static_cast要求公有、可验证的继承路径,否则编译器拒绝转换 - 多重继承下传错模板参数(如
class D : public Base<d>, public Other</d>)可能导致this指针偏移错误,static_cast结果未定义


















