about summary refs log tree commit diff
path: root/absl/types
diff options
context:
space:
mode:
Diffstat (limited to 'absl/types')
-rw-r--r--absl/types/optional.h38
-rw-r--r--absl/types/optional_test.cc50
2 files changed, 79 insertions, 9 deletions
diff --git a/absl/types/optional.h b/absl/types/optional.h
index 5099d4899d6d..3e010bd5d0cf 100644
--- a/absl/types/optional.h
+++ b/absl/types/optional.h
@@ -439,6 +439,33 @@ struct is_constructible_convertible_assignable_from_optional
 // for checking whether an expression is convertible to bool.
 bool convertible_to_bool(bool);
 
+// Base class for std::hash<absl::optional<T>>:
+// If std::hash<std::remove_const_t<T>> is enabled, it provides operator() to
+// compute the hash; Otherwise, it is disabled.
+// Reference N4659 23.14.15 [unord.hash].
+template <typename T, typename = size_t>
+struct optional_hash_base {
+  optional_hash_base() = delete;
+  optional_hash_base(const optional_hash_base&) = delete;
+  optional_hash_base(optional_hash_base&&) = delete;
+  optional_hash_base& operator=(const optional_hash_base&) = delete;
+  optional_hash_base& operator=(optional_hash_base&&) = delete;
+};
+
+template <typename T>
+struct optional_hash_base<T, decltype(std::hash<absl::remove_const_t<T> >()(
+                                 std::declval<absl::remove_const_t<T> >()))> {
+  using argument_type = absl::optional<T>;
+  using result_type = size_t;
+  size_t operator()(const absl::optional<T>& opt) const {
+    if (opt) {
+      return std::hash<absl::remove_const_t<T> >()(*opt);
+    } else {
+      return static_cast<size_t>(0x297814aaad196e6dULL);
+    }
+  }
+};
+
 }  // namespace optional_internal
 
 // -----------------------------------------------------------------------------
@@ -1072,15 +1099,8 @@ namespace std {
 
 // std::hash specialization for absl::optional.
 template <typename T>
-struct hash<absl::optional<T>> {
-  size_t operator()(const absl::optional<T>& opt) const {
-    if (opt) {
-      return hash<T>()(*opt);
-    } else {
-      return static_cast<size_t>(0x297814aaad196e6dULL);
-    }
-  }
-};
+struct hash<absl::optional<T> >
+    : absl::optional_internal::optional_hash_base<T> {};
 
 }  // namespace std
 
diff --git a/absl/types/optional_test.cc b/absl/types/optional_test.cc
index 25b44b171c9d..65f43871e1c0 100644
--- a/absl/types/optional_test.cc
+++ b/absl/types/optional_test.cc
@@ -24,6 +24,17 @@
 #include "absl/meta/type_traits.h"
 #include "absl/strings/string_view.h"
 
+struct Hashable {};
+
+namespace std {
+template <>
+struct hash<Hashable> {
+  size_t operator()(const Hashable&) { return 0; }
+};
+}  // namespace std
+
+struct NonHashable {};
+
 namespace {
 
 std::string TypeQuals(std::string&) { return "&"; }
@@ -1434,6 +1445,17 @@ TEST(optionalTest, ValueType) {
       (std::is_same<absl::optional<int>::value_type, absl::nullopt_t>::value));
 }
 
+template <typename T>
+struct is_hash_enabled_for {
+  template <typename U, typename = decltype(std::hash<U>()(std::declval<U>()))>
+  static std::true_type test(int);
+
+  template <typename U>
+  static std::false_type test(...);
+
+  static constexpr bool value = decltype(test<T>(0))::value;
+};
+
 TEST(optionalTest, Hash) {
   std::hash<absl::optional<int>> hash;
   std::set<size_t> hashcodes;
@@ -1442,6 +1464,34 @@ TEST(optionalTest, Hash) {
     hashcodes.insert(hash(i));
   }
   EXPECT_GT(hashcodes.size(), 90);
+
+  static_assert(is_hash_enabled_for<absl::optional<int>>::value, "");
+  static_assert(is_hash_enabled_for<absl::optional<Hashable>>::value, "");
+
+#if defined(_MSC_VER) || (defined(_LIBCPP_VERSION) && \
+                          _LIBCPP_VERSION < 4000 && _LIBCPP_STD_VER > 11)
+  // For MSVC and libc++ (< 4.0 and c++14), std::hash primary template has a
+  // static_assert to catch any user-defined type that doesn't provide a hash
+  // specialization. So instantiating std::hash<absl::optional<T>> will result
+  // in a hard error which is not SFINAE friendly.
+#define ABSL_STD_HASH_NOT_SFINAE_FRIENDLY 1
+#endif
+
+#ifndef ABSL_STD_HASH_NOT_SFINAE_FRIENDLY
+  static_assert(!is_hash_enabled_for<absl::optional<NonHashable>>::value, "");
+#endif
+
+  // libstdc++ std::optional is missing remove_const_t, i.e. it's using
+  // std::hash<T> rather than std::hash<std::remove_const_t<T>>.
+  // Reference: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82262
+#ifndef __GLIBCXX__
+  static_assert(is_hash_enabled_for<absl::optional<const int>>::value, "");
+  static_assert(is_hash_enabled_for<absl::optional<const Hashable>>::value, "");
+  std::hash<absl::optional<const int>> c_hash;
+  for (int i = 0; i < 100; ++i) {
+    EXPECT_EQ(hash(i), c_hash(i));
+  }
+#endif
 }
 
 struct MoveMeNoThrow {