使用C++26显式对象参数替代CRTP时Clang编译失败问题排查
问题背景
尝试用C++23引入的显式对象参数(P0847R7《Deducing this》)替代奇异递归模板模式(CRTP)实现简单继承体系,但在Clang 20.1.2编译时出错:编译器提示基类shape中找不到draw_impl方法,而该方法实际存在于派生类triangle中,这与提案及CppCon 2023相关演讲示例矛盾。
最小复现示例
#include <print> struct shape { void draw(this auto&& self) { return self.draw_impl(); // 错误:'shape'中没有名为'draw_impl'的成员 } }; struct triangle : public shape { void draw_impl() { std::println("{}", "triangle"); } }; void draw_shapes(shape& type) { type.draw(); } auto main() -> int { auto tri = triangle{}; draw_shapes(tri); return 0; }
编译报错信息
使用Clang 20.1.2 + -std=c++2c编译时,报错:
main.cpp:5:16: error: no member named 'draw_impl' in 'shape' 5 | return self.draw_impl(); | ~~~~ ^ main.cpp:16:8: note: in instantiation of function template specialization 'shape::draw<shape &>' requested here 16 | type.draw(); | ^ 1 error generated.
问题解答
1. 这是Clang的限制或Bug吗?
这是Clang当前实现的限制,而非C++标准本身的问题。当通过基类引用shape&调用draw时,Clang会将auto&& self推导为shape&,在模板实例化阶段直接检查shape类是否存在draw_impl方法——但shape本身并没有这个方法,因此报错。
而C++标准允许这种“延迟依赖查找”:在实际调用点,self的动态类型是triangle&,应该在派生类中查找draw_impl。Clang尚未完全实现这部分逻辑,属于版本迭代中的滞后问题。
2. 显式对象参数的设计意图是否变更?
没有变更。P0847R7及后续纳入C++26的版本中,显式对象参数的核心设计目标之一就是简化CRTP式的非虚多态,允许基类方法委托给派生类实现,且基类无需模板化。你的用法完全符合设计意图,只是编译器实现未跟上标准要求。
3. 其他编译器的支持情况?
- GCC 13+:可以正常编译运行,GCC正确处理了依赖查找逻辑,会在实际的派生类类型中查找
draw_impl。 - MSVC 17.8+:同样可以正常编译,完整支持这种用法。
你遗漏的点是:Clang当前版本对显式对象参数的依赖查找支持不完善,尤其是通过基类引用调用时的类型推导后成员查找逻辑。
解决办法
方案1:添加概念约束(推荐)
给draw方法添加概念约束,明确要求self类型拥有draw_impl方法,同时帮助Clang正确触发依赖查找:
#include <print> #include <concepts> template<typename T> concept Drawable = requires(T& t) { t.draw_impl(); }; struct shape { void draw(this auto&& self) requires Drawable<decltype(self)> { return self.draw_impl(); } }; struct triangle : public shape { void draw_impl() { std::println("{}", "triangle"); } }; void draw_shapes(shape& type) { type.draw(); } auto main() -> int { auto tri = triangle{}; draw_shapes(tri); return 0; }
方案2:CRTP兼容写法(退而求其次)
如果需要兼容更多编译器,可以结合显式对象参数和CRTP,保留基类模板化:
#include <print> template<typename Derived> struct shape { void draw(this auto&& self) { static_cast<Derived&>(self).draw_impl(); } }; struct triangle : public shape<triangle> { void draw_impl() { std::println("{}", "triangle"); } }; void draw_shapes(auto& type) { type.draw(); } auto main() -> int { auto tri = triangle{}; draw_shapes(tri); return 0; }
方案3:传统运行时多态(兼容性最优)
如果不排斥虚函数的运行时开销,可以用传统虚函数实现,兼容性最好:
#include <print> struct shape { virtual void draw_impl() = 0; void draw() { draw_impl(); } virtual ~shape() = default; }; struct triangle : public shape { void draw_impl() override { std::println("{}", "triangle"); } }; void draw_shapes(shape& type) { type.draw(); } auto main() -> int { auto tri = triangle{}; draw_shapes(tri); return 0; }
内容的提问来源于stack exchange,提问作者Aaron Chifwalo Shavesha

