Library: automatic bridge to use boost::hash functions for std::hash

NOTE: this header contains a potentially dangerous, temporary workaround
to defeat the static assertion in the default implementation of std::hash,
as shipped with GCC 4.7.x

This assertion turns out to be detrimental all kinds of metaprogramming
based solutions, since it defeats SFINAE. It is expected to be removed
in GCC 4.8
This commit is contained in:
Fischlurch 2014-08-17 07:15:47 +02:00
parent f02481bb90
commit 9a95beda32

View file

@ -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 <b>includes and manipulates</b> the standard header \c <functional>. 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 <bits/functional_hash.> has already been included. \
Please ensure that lib/hash_standard.hpp is included first, before including <string> or <functional>"
#endif
#include "lib/hash-value.h"
#include "lib/meta/util.hpp"
#include <cstddef>
#include <boost/utility/enable_if.hpp>
//#include <boost/functional/hash.hpp>
#include <functional>
namespace lib {
namespace meta {
namespace {
struct NoUsableHashDefinition { size_t more_than_one[2]; };
typedef size_t HasUsableHashDefinition;
NoUsableHashDefinition hash_value(...);
}
template <typename T>
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<typename TY>
struct provides_BoostHasher
class provides_BoostHashFunction
{
template<class X>
static Yes_t check(typename has_boost_hash_subst<decltype(hash_value(X()))>::Type);
template<class>
static No_t check(...);
enum{ value = (sizeof(Yes_t)==sizeof(check<TY>(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<typename Result, typename Arg>
struct __hash_base;
template<typename TY, typename TOGGLE = void>
struct _HashImplementationSelector
: public __hash_base<size_t, TY>
{
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<typename TY>
struct hash< boost::enable_if<lib::meta::provides_BoostHasher<TY>,
TY > >
{
size_t
operator() (TY const& val) const noexcept
{
return hash_value(val);
}
};
struct _HashImplementationSelector<TY, typename boost::enable_if< lib::meta::provides_BoostHashFunction<TY> >::type >
: public __hash_base<size_t, TY>
{
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<typename TY>
struct hash
: public _HashImplementationSelector<TY>
{ };
}
#endif
/* ==== Evil Evil ==== */
#define hash hash_HIDDEN
#define _Hash_impl _Hash_impl_HIDDEN
#include <bits/c++config.h>
#include <bits/functional_hash.h>
#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<typename ... ARGS>
static auto
hash (ARGS&&... args) -> decltype(hash_HIDDEN (std::forward<ARGS>(args)...))
{
return hash_HIDDEN (std::forward<ARGS>(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*/