CRTP框架下基于基类实现std::hash派生类特化编译失败求助
问题分析
你遇到的核心问题是:std::hash<Base<Self>>的模板特化不会自动应用到派生类。std::hash的特化是基于具体类型精准匹配的,Derived_A和Base<Derived_A>是两个完全独立的类型,因此编译器找不到std::hash<Derived_A>的对应特化,只能尝试调用标准库的默认哈希实现——而标准库对非内置、非枚举且无自定义特化的类型,会触发被删除的__enum_hash构造,最终导致编译报错。
解决方案
要实现基于CRTP基类的通用哈希特化,需要让std::hash能匹配所有继承自Base<Self>的派生类。下面提供两种可行方案:
方案一:SFINAE通用特化(无需逐个写派生类特化)
通过SFINAE(替换失败并非错误)约束模板特化,仅对CRTP体系内的派生类生效:
1. 给基类添加标识 trait
先在基类中加入一个类型标记,用于后续检测类型归属:
#include <vector> #include <functional> #include <type_traits> template<typename Self> class Base { protected: using my_type = int; std::vector<my_type> bar; public: // 标记类型,用于检测是否属于CRTP体系 using is_crtp_derived = std::true_type; virtual void baz(void) const = 0; Self foo(void) const { // CRTP场景下用static_cast更安全,避免dynamic_cast的运行时开销 Self result = *static_cast<const Self*>(this); // 对bar成员的操作逻辑 return result; }; };
2. 编写通用std::hash特化
利用SFINAE过滤出带有is_crtp_derived标记的类型:
namespace std { template<typename T> struct hash<T, std::enable_if_t<std::is_same_v<typename T::is_crtp_derived, std::true_type>>> { std::size_t operator()(const T& obj) const noexcept { // 安全转换到基类,访问受保护的bar成员 const auto& base_obj = static_cast<const Base<T>&>(obj); // 自定义哈希逻辑示例,可根据需求修改 std::size_t hash_val = 0; for (int val : base_obj.bar) { hash_val ^= std::hash<int>{}(val) + 0x9e3779b9 + (hash_val << 6) + (hash_val >> 2); } return hash_val; } }; }
3. 派生类无需额外修改
原来的Derived_A和Derived_B可以直接使用,不需要添加任何额外代码:
class Derived_A final : public Base<Derived_A> { public: void baz(void) const override {} }; class Derived_B : public Base<Derived_B> { public: void baz(void) const override {} };
方案二:派生类特化复用基类逻辑(更直观)
如果不想用SFINAE,也可以在基类中定义哈希核心逻辑,再让每个派生类的std::hash特化直接继承基类的特化:
1. 基类添加哈希方法
template<typename Self> class Base { protected: using my_type = int; std::vector<my_type> bar; public: virtual void baz(void) const = 0; Self foo(void) const { Self result = *static_cast<const Self*>(this); // 对bar的操作逻辑 return result; }; // 核心哈希逻辑,供特化调用 std::size_t compute_hash() const noexcept { std::size_t hash_val = 0; for (int val : bar) { hash_val ^= std::hash<int>{}(val) * 31; } return hash_val; } };
2. 基类与派生类的std::hash特化
// 基类的通用特化 template<typename Self> struct std::hash<Base<Self>> { std::size_t operator()(const Base<Self>& obj) const noexcept { return obj.compute_hash(); } }; // 派生类特化直接继承基类特化,无需重复写逻辑 template<> struct std::hash<Derived_A> : std::hash<Base<Derived_A>> {}; template<> struct std::hash<Derived_B> : std::hash<Base<Derived_B>> {};
验证修改后的使用代码
int main() { Derived_A d_a; Derived_B d_b; auto d_a_ = d_a.foo(); d_a.baz(); std::hash<Derived_B>{}(d_b); std::hash<Derived_A>{}(d_a); // 现在可以正常编译 }
内容的提问来源于stack exchange,提问作者oliversm
相关产品推荐
相关产品推荐

