You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用C++26显式对象参数替代CRTP时Clang编译失败问题排查

显式对象参数替代CRTP的编译问题分析与解决

问题背景

尝试用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:15:08