CRTP对象不能放进同一std::vector,因为Base和Base是完全无关类型,无公共基类,不支持类型擦除或运行时多态。

CRTP 不是“写对模板参数就能跑通”的语法技巧,而是编译期强契约:派生类类型必须显式传入、函数签名必须字面匹配、所有调用路径在实例化时就完全确定——错一处,编译即停。
Base 的模板参数为什么不能省略或泛化
CRTP 基类不是普通基类,它依赖 Derived 类型完整定义才能生成可调用的成员函数。漏写、写成 Base<void></void> 或 Base<t></t> 都会导致编译失败或未定义行为。
-
class Circle : public Base {}→ 编译错误:Base是不完整类型,无法实例化 -
class Circle : public Base<shape></shape>(Shape是中间基类)→static_cast<shape>(this)</shape>可能越界,ABI 不保证偏移安全 -
class Vec3f : public Base<vec3>></vec3>→ 正确,但必须写全特化名,不能简写为Vec3,否则类型不匹配
根本原因:只有 Base<derived></derived> 实例化后,编译器才真正“看见” Derived::draw_impl() 的签名,进而校验 static_cast<derived>(this)->draw_impl()</derived> 是否合法。
派生类函数签名必须和基类调用点严格一致
CRTP 没有运行时名称查找、重载解析或 const 适配能力。基类中写 draw_impl(),派生类就必须提供同名、同参数、同 cv 限定的公有成员函数,否则报错位置往往指向 static_cast 行,而非缺失定义处。
立即学习“C++免费学习笔记(深入)”;
- 基类调
draw_impl(int),派生类写draw_impl(float)→ 编译失败,提示 “no member named 'draw_impl'” - 基类调非 const 版本,派生类只提供
void draw_impl() const→ 编译失败,const 不匹配 - 想支持多种接口?得用
requires(C++20)或std::is_detected_vtrait +if constexpr分支,不能靠“自动降级”
建议在基类关键调用点加 static_assert 显式检查,例如:static_assert(std::is_same_v<decltype>().getValue()), int>, "Derived::getValue() must return int");</decltype>
为什么 CRTP 对象不能放进 std::vector > 统一持有
Base<circle></circle> 和 Base<square></square> 是两个完全无关的类型,没有公共基类,也不满足任何隐式转换关系。C++ 没有“模板类型擦除”语法糖,std::vector 要求元素类型完全相同。
-
std::vector<base>>语法非法 ——auto不能用于模板实参推导(C++20 也不支持) - 试图用
std::vector<:unique_ptr>>></:unique_ptr>存多个不同特化?不行,Base<square></square>无法转型为Base<circle>*</circle> - 真需要运行时混合行为,只能组合方案:内部用 CRTP 做计算核心,外层再继承虚基类(如
class Circle : public ShapeBase<circle>, public Drawable</circle>),或改用std::variant<circle square></circle>+std::visit
这并非缺陷,而是设计取舍:CRTP 放弃了运行时类型统一的能力,换来的是零虚表、零间接跳转、零对象尺寸开销 —— 你得清楚自己究竟要什么。
CRTP 实例化失败时,错误信息为什么总指向基类而不是派生类
因为错误发生在模板实例化阶段,而基类模板里那行 static_cast<derived>(this)->foo()</derived> 是触发点。编译器看到这个表达式,才去查找 Derived::foo;找不到时,它只告诉你“这里调用了不存在的东西”,不会主动回溯到派生类定义处补全上下文。
- 典型报错:
error: no member named 'foo' in 'Circle'—— 实际是因为Circle拼错了函数名,或忘了声明 - 若派生类定义在另一个头文件且未被包含,还可能报
error: 'Circle' is an incomplete type - 解决方案:把派生类定义放在基类模板定义之后、同一翻译单元内;或使用显式实例化
template class Base<circle>;</circle>并在 .cpp 中提供实现
最易被忽略的一点:CRTP 的安全边界仅限于标准单继承布局。一旦涉及虚继承、多重继承(尤其非首继承),static_cast<derived>(this)</derived> 的指针偏移不再可预测,行为未定义 —— 这时候别硬扛,该用虚函数就用虚函数。


















