LUMIERA.clone/src/lib/hash-standard.hpp
Ichthyostega 8aa0c258ba Job-Planning: investigate invocation of jobs
...using the MockJobTicket setup as point of reference,
since the actual invocation of render nodes will only be drafted
later in this "Vertical Slice" integration effort...
2023-04-30 02:18:56 +02:00

218 lines
7.5 KiB
C++

/*
HASH-STANDARD.hpp - allow use both of std and boost hasher implementations
Copyright (C) Lumiera.org
2014, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file hash-standard.hpp
** Helper to use a single extension point for specialised hash functions.
** With the switch to C++11, there are now two competing quasi standard frameworks
** for defining individual hasher functions: the \c std::hash functor and \c <boost/functional/hash.hpp>.
** The standard library hasher is used by the (new) STL hashtables and requires an explicit
** specialisation of \c std::hash<TYPE>, while the boost hash automatically picks up an
** free function `hash_value(MyType const&)`. Additionally, Boost ships with a large collection
** 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 -- 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 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 meant 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 `<functional>`.
** Please ensure it is always included _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
** for this base type is not considered on lookup. ///////TICKET #722
**
** @see HashIndexed
** @see LUID
**
*/
#ifndef LIB_HASH_STANDARD_H
#define LIB_HASH_STANDARD_H
#ifdef _FUNCTIONAL_HASH_H
#error "unable to hijack std::hash, since <bits/functional_hash.hpp> 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 <cstddef>
#include <utility>
namespace lib {
namespace meta {
struct NoUsableHashDefinition { size_t more_than_one[2]; };
typedef size_t HasUsableHashDefinition;
NoUsableHashDefinition hash_value(...); ///< declared for metaprogramming only, never defined
/**
* trait template to detect if some custom type TY
* provides a boost compliant hash function through ADL
*/
template<typename TY>
class provides_BoostHashFunction
{
TY const& unusedDummy = *(TY*)nullptr;
public:
enum{ value = (sizeof(HasUsableHashDefinition) == sizeof(hash_value(unusedDummy))) };
};
}}
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 _HashImplementationSelector<TY, std::enable_if_t< lib::meta::provides_BoostHashFunction<TY>::value >>
: 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>
{ };
}
/* ==== 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*/