hash: Add function for efficiently combining hashes

You can now call stdplus::hashMulti(...) with multiple arguments and get
a single resulting hash value.

Change-Id: I82d91f3372daeea941f6b9e8d57a224b5806527d
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index c3bfdfe..358bc32 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -6,6 +6,7 @@
   'stdplus/flags.hpp',
   'stdplus/handle/copyable.hpp',
   'stdplus/handle/managed.hpp',
+  'stdplus/hash.hpp',
   'stdplus/pinned.hpp',
   'stdplus/raw.hpp',
   'stdplus/signal.hpp',
diff --git a/include/stdplus/hash.hpp b/include/stdplus/hash.hpp
new file mode 100644
index 0000000..44cc6a6
--- /dev/null
+++ b/include/stdplus/hash.hpp
@@ -0,0 +1,68 @@
+#pragma once
+#include <cstddef>
+#include <utility>
+
+namespace std
+{
+template <class Key>
+struct hash;
+}
+
+namespace stdplus
+{
+
+template <class Key>
+struct hash;
+
+namespace detail
+{
+
+constexpr std::size_t updateSeed(std::size_t seed, std::size_t v) noexcept
+{
+    return seed ^ (v + 0x9e3779b9 + (seed << 6) + (seed >> 2));
+}
+
+template <typename T>
+constexpr std::size_t
+    hashMultiS(std::size_t seed,
+               const T& t) noexcept(noexcept(hash<T>{}(std::declval<T>())))
+{
+    return updateSeed(seed, hash<T>{}(t));
+}
+
+template <typename T, typename... Ts>
+constexpr std::size_t
+    hashMultiS(std::size_t seed, const T& t, const Ts&... ts) noexcept(
+        (noexcept(hashMultiS(0, std::declval<T>())) &&
+         ...&& noexcept(hashMultiS(0, std::declval<Ts>()))))
+{
+    return hashMultiS(hashMultiS(seed, t), ts...);
+}
+
+} // namespace detail
+
+constexpr std::size_t hashMulti() noexcept
+{
+    return 0;
+}
+
+template <typename T>
+constexpr std::size_t
+    hashMulti(const T& t) noexcept(noexcept(hash<T>{}(std::declval<T>())))
+{
+    return hash<T>{}(t);
+}
+
+template <typename T, typename... Ts>
+constexpr std::size_t hashMulti(const T& t, const Ts&... ts) noexcept(
+    noexcept(hashMulti(std::declval<T>())) && noexcept(
+        detail::hashMultiS(0, std::declval<Ts>()...)))
+{
+    return detail::hashMultiS(hashMulti(t), ts...);
+}
+
+template <class Key>
+struct hash : std::hash<Key>
+{};
+
+} // namespace stdplus
diff --git a/src/hash.cpp b/src/hash.cpp
new file mode 100644
index 0000000..ebba1d7
--- /dev/null
+++ b/src/hash.cpp
@@ -0,0 +1 @@
+#include <stdplus/hash.hpp>
diff --git a/src/meson.build b/src/meson.build
index 7c4dabf..c4662ad 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -47,6 +47,7 @@
   'flags.cpp',
   'handle/copyable.cpp',
   'handle/managed.cpp',
+  'hash.cpp',
   'pinned.cpp',
   'raw.cpp',
   'signal.cpp',
diff --git a/test/hash.cpp b/test/hash.cpp
new file mode 100644
index 0000000..23e0e26
--- /dev/null
+++ b/test/hash.cpp
@@ -0,0 +1,17 @@
+#include <stdplus/hash.hpp>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+TEST(HashMulti, Basic)
+{
+    EXPECT_EQ(0, hashMulti());
+    EXPECT_EQ(2654435834, hashMulti(1, 2));
+    hashMulti(1, std::string("bacon"), nullptr);
+}
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 901527d..98435bf 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -3,6 +3,7 @@
   'exception': [stdplus_dep, gtest_main_dep],
   'handle/copyable': [stdplus_dep, gtest_main_dep],
   'handle/managed': [stdplus_dep, gtest_main_dep],
+  'hash': [stdplus_dep, gtest_main_dep],
   'pinned': [stdplus_dep, gtest_main_dep],
   'raw': [stdplus_dep, gmock_dep, gtest_main_dep],
   'signal': [stdplus_dep, gtest_main_dep],