EricWF created this revision.

This is my attempt to improve the diagnostic messages emitted from the 
unordered containers when the supplied hash doesn't meet the `Hash` 
requirements.

Unfortunately producing reasonable diagnostics is tricky and it requires a 
bunch of meta-programming to get right.

@thakis Does this look more reasonable to you?


https://reviews.llvm.org/D30597

Files:
  include/__hash_table
  include/utility
  test/libcxx/containers/unord/unord.set/missing_hash_specialization.fail.cpp

Index: test/libcxx/containers/unord/unord.set/missing_hash_specialization.fail.cpp
===================================================================
--- test/libcxx/containers/unord/unord.set/missing_hash_specialization.fail.cpp
+++ test/libcxx/containers/unord/unord.set/missing_hash_specialization.fail.cpp
@@ -10,18 +10,16 @@
 // REQUIRES: diagnose-if-support
 // UNSUPPORTED: c++98, c++03
 
-// Libc++ only provides a defined primary template for std::hash in C++14 and
-// newer.
-// UNSUPPORTED: c++11
-
 // <unordered_set>
 
 // Test that we generate a reasonable diagnostic when the specified hash is
 // not enabled.
 
 #include <unordered_set>
 #include <utility>
 
+#include "test_macros.h"
+
 using VT = std::pair<int, int>;
 
 struct BadHashNoCopy {
@@ -47,21 +45,22 @@
 
   {
     using Set = std::unordered_set<VT>;
-    Set s; // expected-error@__hash_table:* {{the specified hash does not meet the Hash requirements}}
-
-
-  // FIXME: It would be great to suppress the below diagnostic all together.
-  //        but for now it's sufficient that it appears last. However there is
-  //        currently no way to test the order diagnostics are issued.
-  // expected-error@memory:* {{call to implicitly-deleted default constructor of 'std::__1::hash<std::__1::pair<int, int> >'}}
+    Set s; // expected-error@utility:* {{no matching specialization for std::hash<Key>}}
+#if TEST_STD_VER == 11
+    // expected-error@type_traits:* {{implicit instantiation of undefined template}}
+    // expected-error@memory:* {{no member named 'value'}}
+    // expected-error@memory:* {{multiple overloads of '__compressed_pair'}}
+#else
+    // expected-error@memory:* {{call to implicitly-deleted default constructor}}
+#endif
   }
   {
     using Set = std::unordered_set<int, BadHashNoCopy>;
-    Set s; // expected-error@__hash_table:* {{the specified hash does not meet the Hash requirements}}
+    Set s; // expected-error@utility:* {{the specified hash is required to be copy constructible}}
   }
   {
     using Set = std::unordered_set<int, BadHashNoCall>;
-    Set s; // expected-error@__hash_table:* {{the specified hash does not meet the Hash requirements}}
+    Set s; // expected-error@utility:* {{the specified hash is required to be callable type}}
   }
   {
     using Set = std::unordered_set<int, GoodHashNoDefault>;
Index: include/utility
===================================================================
--- include/utility
+++ include/utility
@@ -1535,6 +1535,7 @@
 };
 template <class _Tp>
 struct _LIBCPP_TEMPLATE_VIS __enum_hash<_Tp, false> {
+    using __is_invalid_std_hash = true_type;
     __enum_hash() = delete;
     __enum_hash(__enum_hash const&) = delete;
     __enum_hash& operator=(__enum_hash const&) = delete;
@@ -1560,16 +1561,90 @@
 #endif
 
 #ifndef _LIBCPP_CXX03_LANG
+#if _LIBCPP_STD_VER > 11
+template <class _Hash, class = void>
+struct __is_invalid_std_hash : false_type {};
+
+template <class _Arg>
+struct __is_invalid_std_hash<
+    _Arg,
+    typename __void_t<typename _Arg::__is_invalid_std_hash>::type
+> : true_type {};
+#else
+template <class _Hash>
+struct __is_hash_specialization : false_type {};
+
+template <class _Arg>
+struct __is_hash_specialization<hash<_Arg>> : true_type {};
+
+template <class _Hash, class = void>
+struct __is_invalid_std_hash
+    : integral_constant<bool, __is_hash_specialization<_Hash>::value> {};
+
+template <class _Arg>
+struct __is_invalid_std_hash<hash<_Arg>,
+    typename __void_t<integral_constant<size_t, sizeof(hash<_Arg>)>>::type
+> : false_type {};
+
+
+#endif
+
+template <class _Key, class _Hash, bool _EnableAssertions = false,
+          bool _IsStdHashInvalid = __is_invalid_std_hash<_Hash>::value,
+          class = typename enable_if<_IsStdHashInvalid>::type>
+constexpr bool __check_hash_requirements() {
+  static_assert(!_IsStdHashInvalid || !_EnableAssertions,
+    "no matching specialization for std::hash<Key>");
+  return false;
+}
+
+template <class _Key, class _Hash, bool _EnableAssertions = false,
+          class _StdHashIsInvalid = __is_invalid_std_hash<_Hash>,
+          class = typename enable_if<!_StdHashIsInvalid::value>::type,
+          bool _IsCopyConstructible = is_copy_constructible<_Hash>::value,
+          bool _IsMoveConstructible = is_move_constructible<_Hash>::value,
+          bool _HasCallOperator = __invokable<_Hash, _Key const&>::value,
+          bool _HasCorrectReturnType = __invokable_r<size_t, _Hash, _Key const&>::value,
+          bool _IsGood = _IsCopyConstructible
+                      && _IsMoveConstructible && _HasCallOperator
+                      && _HasCorrectReturnType>
+constexpr bool __check_hash_requirements() {
+  static_assert(_IsCopyConstructible || !_EnableAssertions,
+    "the specified hash is required to be copy constructible");
+  static_assert(_IsMoveConstructible  || !_IsCopyConstructible || !_EnableAssertions,
+    "the specified hash is required to be copy and move constructible");
+  static_assert(_HasCallOperator || !_EnableAssertions,
+    "the specified hash is required to be callable type");
+  static_assert(_HasCorrectReturnType || !_HasCallOperator || !_EnableAssertions,
+    "the specified hash is required to return an type convertible to size_t");
+
+  return _IsGood;
+}
+
+
 template <class _Key, class _Hash>
-using __check_hash_requirements = integral_constant<bool,
-    is_copy_constructible<_Hash>::value &&
-    is_move_constructible<_Hash>::value &&
-    __invokable_r<size_t, _Hash, _Key const&>::value
->;
+constexpr
+typename enable_if<_VSTD::__check_hash_requirements<_Key, _Hash>(), bool>::type
+__diagnose_hash_requirements_and_warnings()
+    _LIBCPP_DIAGNOSE_WARNING(!__invokable<_Hash const&, _Key const&>::value,
+      "the specified hash does not provide a const call operator")
+{
+  return true;
+}
+
+template <class _Key, class _Hash>
+constexpr
+typename enable_if<!_VSTD::__check_hash_requirements<_Key, _Hash>(), bool>::type
+__diagnose_hash_requirements_and_warnings()
+{
+  return _VSTD::__check_hash_requirements<_Key, _Hash, /*EnableAssertions*/true>()
+    || true;
+}
+
 
 template <class _Key, class _Hash = std::hash<_Key> >
 using __has_enabled_hash = integral_constant<bool,
-    __check_hash_requirements<_Key, _Hash>::value &&
+    __check_hash_requirements<_Key, _Hash>() &&
     is_default_constructible<_Hash>::value
 >;
 
Index: include/__hash_table
===================================================================
--- include/__hash_table
+++ include/__hash_table
@@ -871,18 +871,14 @@
 template <class _Key, class _Hash, class _Equal, class _Alloc>
 struct __diagnose_hash_table_helper {
   static constexpr bool __trigger_diagnostics()
-    _LIBCPP_DIAGNOSE_WARNING(__check_hash_requirements<_Key, _Hash>::value
-                         && !__invokable<_Hash const&, _Key const&>::value,
-      "the specified hash functor does not provide a const call operator")
     _LIBCPP_DIAGNOSE_WARNING(is_copy_constructible<_Equal>::value
                           && !__invokable<_Equal const&, _Key const&, _Key const&>::value,
       "the specified comparator type does not provide a const call operator")
   {
-    static_assert(__check_hash_requirements<_Key, _Hash>::value,
-      "the specified hash does not meet the Hash requirements");
     static_assert(is_copy_constructible<_Equal>::value,
       "the specified comparator is required to be copy constructible");
-    return true;
+    return _VSTD::__diagnose_hash_requirements_and_warnings<_Key, _Hash>()
+        || true;
   }
 };
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to