diff options
Diffstat (limited to 'absl/types')
-rw-r--r-- | absl/types/optional.h | 38 | ||||
-rw-r--r-- | absl/types/optional_test.cc | 50 |
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 { |