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 that ensures that all types in the tuple are exactly MyType.
E.g., this
namespace std {
template<typename... Ts>
requires (sizeof...(Ts) > 0 && 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?
Edit: made sure to check the size of the parameter pack is at least 1 to avoid specializing std::hash<std::tuple<>> which is absolutely undefined behaviour.
The rule, in [namespace.std]/2 is:
Unless explicitly prohibited, a program may add a template specialization for any standard library class template to namespace
stdprovided that (a) the added declaration depends on at least one program-defined type and (b) the specialization meets the standard library requirements for the original template.
Where a program-defined type, from [defns.prog.def.type], is:
non-closure class type or enumeration type that is not part of the C++ standard library and not defined by the implementation, or a closure type of a non-implementation-provided lambda expression, or an instantiation of a program-defined specialization
Your specialization of std::hash for std::tuple<MyType, float> is fine, since that depends on at least one program-defined type (MyType) and otherwise meets the requirement of std::hash.
Your other specialization of std::hash for std::tuple<Ts...> where all the Ts... are specifically MyType is also fine, for the same reason (as long as your constraint eliminates std::tuple<> specifically). That's a program-defined type, too.
It doesn't matter what mechanism you use to create or constrain the specialization, as long as any specialization you do provide depends on at least one program-defined type.
(Note that just providing some constraint is insufficient. My previous version of the answer misread your constraint as simply checking that all the types were the same as each other. That's constrained, but would still create a specialization for a type like std::tuple<std::string, std::string>, which has no program-defined types in it, so would be undefined behavior).
If so, why?
The issue here is ultimately coherence. Who is allowed to specialize what, where. If the standard library can provide a specialization, you don't want to clash with it. More broadly, if multiple libraries try to define specializations for common things, it would be particularly helpful if each library only provided specializations for its own types - otherwise if multiple libraries are trying to provide std::hash<std::tuple<int>>, that's not going to work out well for anybody.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With