diff --git a/src/lib/hash-standard.hpp b/src/lib/hash-standard.hpp index b7be71430..bd8d73173 100644 --- a/src/lib/hash-standard.hpp +++ b/src/lib/hash-standard.hpp @@ -31,11 +31,27 @@ ** of predefined hash functions for various combinations of standard containers and custom types. ** To add to this venerable mess, the standard hash defines a default specialisation which ** triggers a static assertion and halts compilation, in case no custom specialisation - ** can be found. + ** can be found -- which defeats a meta programming solution based on SFINAE. ** ** In the Lumiera code base, the habit is to define a free function \c hash_value alongside with ** custom data types. This helper uses metaprogramming to generate a bridge from the standard hasher - ** to use this boost-style custom hash function if applicable. + ** to use this boost-style custom hash function if applicable. To allow for such an automatic bridge, + ** we have to work around aforementioned problem with the static assertion. Recent discussion threads + ** indicate that the GCC and Clang developers are aware of those likely unintended side effects of a + ** well mentioned hint for people to implement their own hash functions. AFAIK, the standard lib shipping + ** with some GCC 4.8.x doesn't contain the assertion anymore; and there are plans to adopt the boost style + ** extension mechanism and provide such a bridge in the standard library at some point in the future. + ** + ** In the light of this situation, it feels justified to play a dirty trick in order to defeat that + ** static assertion: in this header, we blatantly hijack the standard library definition of std::hash, + ** push it aside and plant our own definition instead. + ** + ** @note this trick was proposed by user "enobayram" on Stackoverflow at Oct 5, 2012 + ** http://stackoverflow.com/questions/12753997/check-if-type-is-hashable + ** + ** @warning this header includes and manipulates the standard header \c . Please + ** ensure it is always included \em before the latter. Failing to do so will result in + ** mysterious failures. ** ** @todo 4/2014 doesn't work as expected. My suspicion is that in the actual use case (PlacementIndex), ** the type providing the hasher is mixed in through inheritance, and the template specialisation @@ -47,58 +63,156 @@ */ - #ifndef LIB_HASH_STANDARD_H #define LIB_HASH_STANDARD_H +#ifdef _FUNCTIONAL_HASH_H +#error "unable to hijack std::hash, since has already been included. \ + Please ensure that lib/hash_standard.hpp is included first, before including or " +#endif -#include "lib/hash-value.h" -#include "lib/meta/util.hpp" +#include #include -//#include -#include + namespace lib { namespace meta { + namespace { + struct NoUsableHashDefinition { size_t more_than_one[2]; }; + typedef size_t HasUsableHashDefinition; + + NoUsableHashDefinition hash_value(...); + + } - template -struct has_boost_hash_subst { typedef void* Type; }; - - - + /** + * trait template to detect if some custom type TY + * provides a boost compliant hash function through ADL + */ template - struct provides_BoostHasher + class provides_BoostHashFunction { - template - static Yes_t check(typename has_boost_hash_subst::Type); - template - static No_t check(...); - - enum{ value = (sizeof(Yes_t)==sizeof(check(nullptr))) }; + TY const& unusedDummy = *(TY*)nullptr; + + public: + enum{ value = (sizeof(HasUsableHashDefinition) == sizeof(hash_value(unusedDummy))) }; }; }} -namespace std { - /////////////////////////////////////////////////////////////////////////TICKET #722 : attempt to provide a generic bridge to use hash_value - // - // this was my first attempt 4/2014, but doesn't work, probably because of the actual ID being inherited - // see also the attempt at the bottom of hash-indexed.hpp + +namespace std { + + template + struct __hash_base; + template + struct _HashImplementationSelector + : public __hash_base + { + static_assert (sizeof(TY) < 0, "No hash implementation found. " + "Either specialise std::hash or provide a boost-style hash_value via ADL."); + + // the default provides *no* hash implementation + // and adds a marker type for metaprogramming + typedef int NotHashable; + }; + + /** + * Specialisation: Bridge from std::hash to boost::hash + */ template - struct hash< boost::enable_if, - TY > > - { - size_t - operator() (TY const& val) const noexcept - { - return hash_value(val); - } - }; + struct _HashImplementationSelector >::type > + : public __hash_base + { + size_t + operator() (TY const& elm) const noexcept + { + return hash_value(elm); + } + }; + /** + * Primary class template for std::hash. + * We provide no default implementation, but a marker type + * to allow detection of custom implementation through metaprogramming + * + * @note this definition has been \em hijacked by Lumiera + * to add an automatic bridge to use boost::hash functions + */ + template + struct hash + : public _HashImplementationSelector + { }; + } -#endif + + + + +/* ==== Evil Evil ==== */ + +#define hash hash_HIDDEN +#define _Hash_impl _Hash_impl_HIDDEN +#include +#include +#undef hash +#undef _Hash_impl + + +namespace std { + + /* after the manipulation is performed now, + * we have to do some clean-up: it might happen that + * other parts of the standard library call directly into the + * standard Hash implementation (in fact, the hashers for strings, + * floats and doubles do this). Thus we have to amend this standard + * implementation, and re-wire it to the original definition. + */ + + struct _Hash_impl + : public std::_Hash_impl_HIDDEN + { + template + static auto + hash (ARGS&&... args) -> decltype(hash_HIDDEN (std::forward(args)...)) + { + return hash_HIDDEN (std::forward(args)...); + } + }; + + /* and likewise, we have to re-wire all the + * default hash implementations to the original implementation. + * This is just a defensive approach; we change a function name, + * yet we avoid changing any actual library code. + */ +#define STD_HASH_IMPL(_TY_) \ + template<> struct hash<_TY_> : public hash_HIDDEN<_TY_> { }; + + STD_HASH_IMPL (bool) + STD_HASH_IMPL (char) + STD_HASH_IMPL (signed char) + STD_HASH_IMPL (unsigned char) + STD_HASH_IMPL (wchar_t) + STD_HASH_IMPL (char16_t) + STD_HASH_IMPL (char32_t) + STD_HASH_IMPL (short) + STD_HASH_IMPL (int) + STD_HASH_IMPL (long) + STD_HASH_IMPL (long long) + STD_HASH_IMPL (unsigned short) + STD_HASH_IMPL (unsigned int) + STD_HASH_IMPL (unsigned long) + STD_HASH_IMPL (unsigned long long) + STD_HASH_IMPL (float) + STD_HASH_IMPL (double) + STD_HASH_IMPL (long double) + +#undef STD_HASH_IMPL +} + +#endif /*LIB_HASH_STANDARD_H*/