c++11stdhash

Why is std::hash not an overloaded function?


I'm guessing std::hash is defined as a template struct in order to avoid implicit type conversions done during overloaded function resolution. Is it a correct thing to say?

I mean, I would prefer to write

std::string s;
size_t hash = std::hash(s);

instead of

std::string s;
size_t hash = std::hash<std::string>()(s);

But I'm guessing there's a reason for why the standards committee has chosen the second option.


Solution

  • It's impossible to partially specialize function template, and so for user-defined templated classes, the would be no way to specialize std::hash if it was a function. (you can only specialize templates from std namespace, but not overload, so users of your templated class couldn't create std::unordered_map<MyClass<whatever>, MyOtherClass>, they would be forced to choose std::unordered_map<MyClass<whatever>, MyOtherClass, ???>). So functor is solution here.

    namespace std
    {
        template<typename T>
        struct hash<MyVector<T>>
        {
            size_t operator()(const MyVector<T>& v)
            {
                //return hash from here
            }
        };
    }
    

    The alternative way for standard library would be using some SFINAE template trick to choose member .hash() as the default, and standard hash if the other case, but in most cases you can't retrofit interfaces (especially if using third-party code)

    The other alternative would be like std::swap does (tricks with ADL):

    //somewhere in std::unordered_map
    using std::hash;
    size_t h = hash(key);
    

    In my experience, ADL is tricky and not everyone remembers about corner cases. Furthermore, the advantage of functors here is fact that you can use them as a template parameter, so you can just plug-in another functor for template (like std::unordered_map<A, B, specialized_hash<A>>) if you think that the default is wrongly suited for your case.

    From comments:

    But could you elaborate a bit more about std::swap? It's still there in C++11 and it has no problems with user-defined types, has it? Why keep a lot of different concepts in STL rather than making it more consistent?

    There's a little difference between std::swap and std::hash:

    In std::hash:

    In std::swap:

    Why is STL inconsistent? I can only guess here, but main reason why STL is inconsistent is (a) bawkward compatibility and (b) C++ also is quite inconsistent.