How do I combine hash values in C++0x?

C++C++11BoostHashStd

C++ Problem Overview


C++0x adds hash<...>(...).

I could not find a hash_combine function though, as presented in boost. What is the cleanest way to implement something like this? Perhaps, using C++0x xor_combine?

C++ Solutions


Solution 1 - C++

Well, just do it like the boost guys did it:

template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}

Solution 2 - C++

I'll share it here since it can be useful to others looking for this solution: starting from @KarlvonMoor answer, here's a variadic template version, which is terser in its usage if you have to combine several values together:

inline void hash_combine(std::size_t& seed) { }

template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
    hash_combine(seed, rest...);
}

Usage:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);

This was written originally to implement a variadic macro to easily make custom types hashable (which I think is one of the primary usages of a hash_combine function):

#define MAKE_HASHABLE(type, ...) \
    namespace std {\
        template<> struct hash<type> {\
            std::size_t operator()(const type &t) const {\
                std::size_t ret = 0;\
                hash_combine(ret, __VA_ARGS__);\
                return ret;\
            }\
        };\
    }

Usage:

struct SomeHashKey {
    std::string key1;
    std::string key2;
    bool key3;
};

MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
// now you can use SomeHashKey as key of an std::unordered_map

Solution 3 - C++

A few days ago I came up with slightly improved version of [this answer][1] (C++ 17 support is required):

template <typename T, typename... Rest>
void hashCombine(uint& seed, const T& v, Rest... rest)
{
    seed ^= ::qHash(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    (hashCombine(seed, rest), ...);
}

The code above is better in terms of code generation. I used qHash function from Qt in my code, but it's also possible to use any other hashers. [1]: https://stackoverflow.com/a/38140932/3476541

Solution 4 - C++

This could also be solved by using a variadic template as follows:

#include <functional>

template <typename...> struct hash;

template<typename T> 
struct hash<T> 
	: public std::hash<T>
{
	using std::hash<T>::hash;
};


template <typename T, typename... Rest>
struct hash<T, Rest...>
{
	inline std::size_t operator()(const T& v, const Rest&... rest) {
		std::size_t seed = hash<Rest...>{}(rest...);
		seed ^= hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
		return seed;
	}
};

Usage:

#include <string>

int main(int,char**)
{
	hash<int, float, double, std::string> hasher;
	std::size_t h = hasher(1, 0.2f, 2.0, "Hello World!");
}

One could certainly make a template function, but this could cause some nasty type deduction e.g hash("Hallo World!") will calculate a hash value on the pointer rather than on the string. This is probably the reason, why the standard uses a struct.

Solution 5 - C++

The answer by vt4a2h is certainly nice but uses the C++17 fold expression and not everyone is able to switch to a newer toolchain easily. The version below uses the expander trick to emulate a fold expression and works in C++11 and C++14 as well.

Additionally, I marked the function inline and use perfect forwarding for the variadic template arguments.

template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    (int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
}

Live example on Compiler Explorer

Solution 6 - C++

I really like the C++17 approach from the answer by vt4a2h, however it suffers from a problem: The Rest is passed on by value whereas it would be more desirable to pass them on by const references (which is a must if it shall be usable with move-only types).

Here is the adapted version which still uses a fold expression (which is the reason why it requires C++17 or above) and uses std::hash (instead of the Qt hash function):

template <typename T, typename... Rest>
void hash_combine(std::size_t& seed, const T& v, const Rest&... rest)
{
	seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
	(hash_combine(seed, rest), ...);
}

For completeness sake: All the types which shall be usable with this version of hash_combine must have a template specialization for hash injected into the std namespace.

Example:

namespace std // Inject hash for B into std::
{
	template<> struct hash<B>
	{
		std::size_t operator()(B const& b) const noexcept
		{
			std::size_t h = 0;
			cgb::hash_combine(h, b.firstMember, b.secondMember, b.andSoOn);
			return h;
		}
	};
}

So that type B in the example above is also usable within another type A, like the following usage example shows:

struct A
{
	std::string mString;
	int mInt;
	B mB;
	B* mPointer;
}

namespace std // Inject hash for A into std::
{
	template<> struct hash<A>
	{
		std::size_t operator()(A const& a) const noexcept
		{
			std::size_t h = 0;
			cgb::hash_combine(h,
				a.mString,
				a.mInt,
				a.mB, // calls the template specialization from above for B
				a.mPointer // does not call the template specialization but one for pointers from the standard template library
			);
			return h;
		}
	};
}

Solution 7 - C++

You can use the rst C++ library that I developed to do that:

#include "rst/stl/hash.h"

struct Point {
  Point(const int x, const int y) : x(x), y(y) {}

  int x = 0;
  int y = 0;
};

bool operator==(const Point lhs, const Point rhs) {
  return (lhs.x == rhs.x) && (lhs.y == rhs.y);
}

namespace std {

template <>
struct hash<Point> {
  size_t operator()(const Point point) const {
    return rst::HashCombine({point.x, point.y});
  }
};

}

Solution 8 - C++

The answer by Henri Menke works great, but if you treat warnings as errors with for example:

add_compile_options(-Werror)

GCC 9.3.0 will give this error:

Test.h:223:67: error: ISO C++ forbids compound-literals [-Werror=pedantic]
  223 |     (int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
      |                                                                  ^
cc1plus: all warnings being treated as errors

We can update the code to avoid the error like this:

template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
    std::hash<T> hasher;
    seed ^= (hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
    int i[] = { 0, (hashCombine(seed, std::forward<Rest>(rest)), 0)... };
    (void)(i);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNeil GView Question on Stackoverflow
Solution 1 - C++Karl von MoorView Answer on Stackoverflow
Solution 2 - C++Matteo ItaliaView Answer on Stackoverflow
Solution 3 - C++vt4a2hView Answer on Stackoverflow
Solution 4 - C++kiloalphaindiaView Answer on Stackoverflow
Solution 5 - C++Henri MenkeView Answer on Stackoverflow
Solution 6 - C++j00hiView Answer on Stackoverflow
Solution 7 - C++Sergey AbbakumovView Answer on Stackoverflow
Solution 8 - C++JohnexView Answer on Stackoverflow