我在示例化一个std::array<int,3>
的std::unordered_set
,发现下面的代码无法通过编译:
#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;
}
错误消息告诉我未检测到特殊化hash<array<T, N>>
。经过一番努力,我发现这是由于非类型模板参数N
的类型与参数(size_t
)的类型不匹配造成的。但是编译器不应该自动将int N
转换为size_t N
吗?由于std::array<int, true>
的使用也通过了编译,正如我在g9.4上测试的那样。C标准中有没有特别提到这种情况?
3条答案
按热度按时间qzlgjiam1#
std::hash
是一个红色的鲱鱼,因为我把@Jason的答案中描述的正式UB解释为w.r.t.关于OP的问题:但是编译器不应该自动将
int N
转换为size_t N
吗?一个最小的例子:
temp.class.spec.match(https://timsong-cpp.github.io/cppwp/n4868/temp.class.spec.match)描述了相关规则[emphasismine]:
/1当类模板用于需要类示例化的上下文中时,需要确定示例化是使用主模板还是部分专门化生成。[...]
/2部分专门化匹配给定的实际模板参数列表如果部分专门化的模板参数可以从实际模板参数列表推导出,[...]
从上面的
#1
,实际的模板参数列表是A<4>
,从中我们可以推导出A<std::size_t{4}>
类型。但是,部分专门化的模板参数列表是A<int{N}>
,它与实际的模板参数列表不匹配。事实上,部分特化#1将永远不会被使用(给定当前示例),因为我们无法生成匹配它的模板参数列表。vi4fp9gy2#
您还需要将散列函数添加到模板参数中。这里是你的代码的修订版本(也有一些小的反馈)。
nkoocmlb3#
将
std::hash
专门化为std::array<int, N>
会导致未定义行为。从[namespace.std]:
除非明确禁止,否则程序可以将任何标准库类模板的模板特化添加到命名空间std,前提是
(a)添加的声明依赖于至少一个程序定义的类型和
(b)该专门化满足原始模板的标准库要求。
(强调我的)
但是在您的示例中,至少有一个程序定义类型的第一个要求没有得到满足。来自[defns.prog.def.type]:
非闭包类类型或枚举类型,它不是C++标准库的一部分,也不是由实现定义的,或者非实现提供的lambda表达式的闭包类型,或者程序定义的专门化的示例化。
一种可能的解决方案是使用lambda。