I was instantiating a std::unordered_set of std::array<int,3>, I found that the following code cannot pass compilation:
#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;
}
The error message tells me that the specialization hash<array<T, N>> is not detected. After some hard work, I found that it is caused by the mismatching between the type of non-type template argument N and the type of parameter (size_t). But shouldn't the compiler cast the int N to size_t N automatically? Since the usage of std::array<int, true> also passes the compilation, as I have tested on g++9.4. Is there any specific mention in the C++ standard regarding this particular situation?
std::hashis a red herring, as do I interpret the formal UB described in @Jason's answer to be, w.r.t. to OP's question:A minimal example:
[temp.class.spec.match] describes the relevant rules [emphasis mine]:
The actual template argument list, from
#1above, isA<4>, from which we can deduce the typeA<std::size_t{4}>. The template argument list for the partial specialization, however, isA<int{N}>, which is not a match for the actual template argument list. In fact, the partial specialization #1 will never be used (given the current example), as we cannot produce a template argument list that will match it.