非类型模板实参与形参类型需匹配吗?std::array哈希特化编译问题
问题场景
尝试实例化std::unordered_set<std::array<int,3>>时,以下代码无法通过编译:
#include <iostream> #include <unordered_set> #include <array> namespace std { template <class T> inline void hash_combine(size_t& seed, const T& v) { hash<T> hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } template <typename T, int N> struct hash<array<T, N>> { size_t operator()(array<T, N> arr) const noexcept { size_t hs = 0; for (auto iter = arr.begin(); iter != arr.end(); iter++) { hash_combine(hs, *iter); } return hs; } }; } int main(int argc, char** argv) { std::unordered_set<std::array<int, 3>> arrset; std::cout << arrset.size() << std::endl; return 0; }
编译错误显示std::hash<std::array<T,N>>的特化未被识别,最终因缺少std::array的哈希函数导致失败。
核心原因:非类型模板参数的严格匹配规则
C++标准规定,std::array的第二个模板参数类型是std::size_t,而非int。当你特化std::hash时,使用的是int N作为非类型参数,这和std::array原模板的std::size_t N类型不匹配。
模板特化的匹配要求非类型模板参数的类型必须完全一致,编译器不会对特化的参数类型进行隐式转换。因此,当实例化std::array<int,3>时,实际传递的非类型参数是std::size_t类型的3,而你的特化是针对int N的,两者无法匹配,编译器会忽略这个特化,转而使用默认的std::hash模板——但默认模板没有针对std::array的特化,最终导致编译错误。
为什么std::array<int, true>能正常编译?
这是因为模板实参传递时的隐式转换规则和特化匹配规则不同:当向模板传递非类型实参时,如果实参是常量表达式,且可以隐式转换为模板参数的类型,编译器会自动完成转换。true是bool类型的常量表达式,可以隐式转换为std::size_t类型的1,因此std::array<int, true>等价于std::array<int, 1>,符合编译要求。
但这种转换仅适用于模板实参的传递,不适用于模板特化的匹配——特化必须严格匹配原模板的参数类型,否则无法生效。
修正方案
将特化中的int N改为std::size_t N,确保和std::array的模板参数类型完全匹配:
#include <iostream> #include <unordered_set> #include <array> namespace std { template <class T> inline void hash_combine(size_t& seed, const T& v) { hash<T> hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } template <typename T, std::size_t N> struct hash<array<T, N>> { size_t operator()(const array<T, N>& arr) const noexcept { size_t hs = 0; for (const auto& elem : arr) { hash_combine(hs, elem); } return hs; } }; } int main(int argc, char** argv) { std::unordered_set<std::array<int, 3>> arrset; std::cout << arrset.size() << std::endl; return 0; }
此外,将参数改为const array<T, N>&可以避免不必要的拷贝,使用范围for循环也让代码更简洁。
内容的提问来源于stack exchange,提问作者Nolazuck

