I know that specializing std::hash
for a general std::tuple
is undefined behaviour.
However, what about specializing std::hash
for a specific tuple?
For example,
namespace std {
template<>
struct hash<std::tuple<MyType, float>> {
size_t operator()(const std::tuple<MyType, float>& t) const {
// ...
}
};
}
Or even std::tuple<Ts ...>
with some kind of required is_all_same_as_v<Ts…, MyType>
C 20 constraint.
E.g., this
namespace std {
template<typename... Ts>
requires is_all_same_as_v<Ts..., MyType>
struct hash<std::tuple<Ts...>> {
size_t operator()(const std::tuple<Ts...>& t) const {
// ...
}
};
}
Is that still considered undefined behaviour? If so, why?
CodePudding user response:
Specializing std::hash
for a std::tuple
is not considered undefined behavior in C .
std::hash
is a template class in the C standard library, which means that it can be specialized for different types to define a custom hash function for those types. This is perfectly valid.
However, it is important to note that, in order for a specialization of std::hash
to be well-defined, it must satisfy the requirements for a hash function. These requirements include that the function must be deterministic, ie. must produce the same result for the same input every time it is called (restriction only limited to a single runtime instance; this allows salted hashes preventing collision DoS attacks), and must not throw exceptions.
Additionally, the function must produce different results for different inputs, unless those inputs are equal (according to the operator ==
function).
The code example you mentioned is a specialization of the std::hash
template class for std::tuples
that contain a MyType
and a float
, and it defines a custom hash function for those tuples. This should also work fine in case of custom tuples.
CodePudding user response:
Yes, specializing std::hash for a specific std::tuple type, such as std::tuple<MyType, float>, is still considered undefined behavior in C . This is because the standard does not allow users to specialize the std::hash template for any type, including specific std::tuple types.
The reason for this is that the std::hash template is defined in the standard library, and the standard library is not required to provide a specialization for every possible type. Instead, the standard library provides a default implementation of std::hash that can be used with any type that satisfies the Hash requirements (i.e., it has a operator() function that takes an object of the type being hashed and returns a std::size_t value). This default implementation is defined in terms of the std::hash specialization for std::basic_string, so it will work with any type that can be implicitly converted to a std::basic_string (such as std::string).
If you want to define a custom std::hash specialization for a specific std::tuple type, you can do so by writing your own std::hash specialization that is not in the std namespace. This will not be considered undefined behavior, but it will not be part of the standard library, and it will not be usable with the standard library's hash-based containers (such as std::unordered_map). Instead, you will need to use your custom std::hash specialization with a custom hash-based container that you have implemented yourself.
For example, you could define a custom Hash class like this:
template <typename T>
struct Hash {
size_t operator()(const T& t) const {
// default implementation of std::hash
return std::hash<std::basic_string>()(std::to_string(t));
}
};
And then define a custom std::hash specialization for a specific std::tuple type like this:
template<>
struct Hash<std::tuple<MyType, float>> {
size_t operator()(const std::tuple<MyType, float>& t) const {
// custom implementation of std::hash for std::tuple<MyType, float>
// ...
}
};
You could then use your custom Hash class with a custom hash-based container like this:
template <typename K, typename V, typename H = Hash<K>>
class MyHashMap {
// implementation of a hash-based map
// ...
};
// use MyHashMap with the default Hash specialization for std::tuple<MyType, float>
MyHashMap<std::tuple<MyType, float>, int> my_map;