Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is specializing std::hash for a constrained std::tuple considered undefined behaviour?

Tags:

c++

c++20

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.

like image 924
supernun Avatar asked May 24 '26 13:05

supernun


1 Answers

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 std provided 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.

like image 129
Barry Avatar answered May 26 '26 02:05

Barry



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!