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::hash
is a red herring, as do I interpret the formal UB described in @Jason's answer to be, w.r.t. to OP's question:
But shouldn't the compiler cast the
int N
tosize_t N
automatically?
A minimal example:
#include <cstddef>
template<std::size_t N>
struct A {};
template<typename T>
struct S { S() = delete; };
template<int N>
struct S<A<N>> {}; // #1
S<A<4>> s{}; // error ("use of deleted function")
// picks non-defined primary template
[temp.class.spec.match] describes the relevant rules [emphasis mine]:
/1 When a class template is used in a context that requires an instantiation of the class, it is necessary to determine whether the instantiation is to be generated using the primary template or one of the partial specializations. [...]
/2 A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list, [...]
The actual template argument list, from #1
above, is A<4>
, from which we can deduce the type A<std::size_t{4}>
. The template argument list for the partial specialization, however, is A<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.