Skip to content

Commit

Permalink
Implement new hashing protocol now that Boost.ContainerHash requires …
Browse files Browse the repository at this point in the history
…C++11:

- Hashes trivially scalar types
- For the non scalar types calls unqualified hash_value activating ADL (boost::hash protocol)
- If ADL does not find a viable candidate, a boost::hash direct call is performed via a forward declaration, so that additional candidates are looked up if the user has included boost/container_hash/hash.hpp.

This maintains a high level of compatibility with boost::hash but making Boost.Intrusive independent from Boost.ContainerHash, so that different C++ standard levels can be used and users using different hash functions don't need direct and indirect dependencies brought by Boost.ContainerHash.
  • Loading branch information
igaztanaga committed Dec 31, 2023
1 parent 8b4297b commit 6464d0e
Show file tree
Hide file tree
Showing 9 changed files with 820 additions and 52 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################

/proj/vc14.1ide
6 changes: 5 additions & 1 deletion doc/intrusive.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,11 @@ And they also can receive additional options:
in containers. Default: `equal< std::equal_to<T> >`

* [*`hash<class Hash>`]: Hash function to be used in the container.
Default: `hash< boost::hash<T> >`
Default: internal hash functor that hashes scalar types, triggering unqualified call to
`hash_value`for non-scalar types (boost::hash<T> compatible protocol). If
the ADL call does not find a viable candidate a boost::hash<T>() call is tried
in case the user has included boost/container_hash/hash.hpp and a viable
candidate is found there. Fails otherwise.

* [*`bucket_traits<class BucketTraits>`]: A type that wraps the bucket vector to
be used by the unordered container. Default: a type initialized by the address
Expand Down
277 changes: 277 additions & 0 deletions include/boost/intrusive/detail/hash.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/////////////////////////////////////////////////////////////////////////////
//
// Copyright 2005-2014 Daniel James.
// Copyright 2021, 2022 Peter Dimov.
// Copyright 2024 Ion Gaztañaga.
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// Based on Peter Dimov's proposal
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf
// issue 6.18.
//
// The original C++11 implementation was done by Peter Dimov
// The C++03 porting was done by Ion Gaztanaga.
//
// The goal of this header is to avoid Intrusive's hard dependency on ContainerHash,
// which adds additional dependencies and the minimum supported C++ standard can
// differ between both libraries. However, a compatibility protocol is used so that
// users compatible with ContainerHash are also compatible with Intrusive:
//
// - If users define `hash_value` (as required by boost::hash) for their classes
// are automatically compatible with Intrusive unordered containers.
//
// - If users include boost/container_hash/hash.hpp in their headers, Intrusive
// unordered containers will take advantage of boost::hash compatibility hash functions
// (such as hashing functions for range-compatible types, standard containers, etc.)
//
// See http://www.boost.org/libs/intrusive for documentation.
//
/////////////////////////////////////////////////////////////////////////////

#ifndef BOOST_INTRUSIVE_HASH_HASH_HPP
#define BOOST_INTRUSIVE_HASH_HASH_HPP

#include <boost/intrusive/detail/config_begin.hpp>
#include <boost/intrusive/detail/workaround.hpp>
#include <boost/intrusive/detail/hash_integral.hpp>
#include <boost/intrusive/detail/hash_mix.hpp>
#include <boost/intrusive/detail/hash_combine.hpp>
#include <boost/cstdint.hpp>
#include <climits>
#include <cstring>
#include <cfloat>
#include <boost/intrusive/detail/mpl.hpp>

namespace boost {

template<class T>
struct hash;

} //namespace boost

//Fallback function to call boost::hash if scalar type and ADL fail.
//The user must include boost/container_hash/hash.hpp when to make this call work,
//this allows boost::intrusive to be compatible with boost::hash without
//a mandatory physical (header inclusion) dependency
namespace boost_intrusive_adl
{
template<class T>
inline std::size_t hash_value(const T& v)
{
return boost::hash<T>()(v);
}
}

namespace boost {
namespace intrusive {
namespace detail {

//ADL-based lookup hash call
template <class T>
inline typename detail::disable_if_c<detail::is_scalar<T>::value, std::size_t>::type
hash_value_dispatch(const T& v)
{
//Try ADL lookup, if it fails, boost_intrusive_adl::hash_value will retry with boost::hash
using boost_intrusive_adl::hash_value;
return hash_value(v);
}

template <typename T>
typename enable_if_c<is_enum<T>::value, std::size_t>::type
hash_value( T v )
{
return static_cast<std::size_t>( v );
}

////////////////////////////////////////////////////////////
//
// floating point types
//
////////////////////////////////////////////////////////////

template<class T, std::size_t Bits = sizeof(T) * CHAR_BIT>
struct hash_float_impl;

// float
template<class T> struct hash_float_impl<T, 32>
{
static std::size_t fn( T v )
{
boost::uint32_t w;
std::memcpy( &w, &v, sizeof( v ) );

return w;
}
};

// double
template<class T> struct hash_float_impl<T, 64>
{
static std::size_t fn( T v )
{
boost::uint64_t w;
std::memcpy( &w, &v, sizeof( v ) );

return hash_value( w );
}
};

// 80 bit long double in 12 bytes
template<class T> struct hash_float_impl<T, 96>
{
static std::size_t fn( T v )
{
boost::uint64_t w[ 2 ] = {};
std::memcpy( &w, &v, 80 / CHAR_BIT );

std::size_t seed = 0;

seed = hash_value( w[0] ) + (hash_mix)( seed );
seed = hash_value( w[1] ) + (hash_mix)( seed );

return seed;
}
};

#if (LDBL_MAX_10_EXP == 4932)

// 80 bit long double in 16 bytes
template<class T> struct hash_float_impl<T, 128>
{
static std::size_t fn( T v )
{
boost::uint64_t w[ 2 ] = {};
std::memcpy( &w, &v, 80 / CHAR_BIT );

std::size_t seed = 0;

seed = hash_value( w[0] ) + (hash_mix)( seed );
seed = hash_value( w[1] ) + (hash_mix)( seed );

return seed;
}
};

#elif (LDBL_MAX_10_EXP > 4932)
// 128 bit long double
template<class T> struct hash_float_impl<T, 128>
{
static std::size_t fn( T v )
{
boost::uint64_t w[ 2 ];
std::memcpy( &w, &v, sizeof( v ) );

std::size_t seed = 0;

#if defined(__FLOAT_WORD_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__

seed = hash_value( w[1] ) + (hash_mix)( seed );
seed = hash_value( w[0] ) + (hash_mix)( seed );

#else

seed = hash_value( w[0] ) + (hash_mix)( seed );
seed = hash_value( w[1] ) + (hash_mix)( seed );

#endif
return seed;
}
};
#endif //#if (LDBL_MAX_10_EXP == 4932)

template <typename T>
typename enable_if_c<is_floating_point<T>::value, std::size_t>::type
hash_value( T v )
{
return boost::intrusive::detail::hash_float_impl<T>::fn( v + 0 );
}

////////////////////////////////////////////////////////////
//
// pointer types
//
////////////////////////////////////////////////////////////
// `x + (x >> 3)` adjustment by Alberto Barbati and Dave Harris.
template <class T> std::size_t hash_value( T* const& v )
{
std::size_t x = reinterpret_cast<std::size_t>( v );
return hash_value( x + (x >> 3) );
}

////////////////////////////////////////////////////////////
//
// std::nullptr_t
//
////////////////////////////////////////////////////////////
#if !defined(BOOST_NO_CXX11_NULLPTR)
template <typename T>
typename enable_if_c<is_same<T, std::nullptr_t>::value, std::size_t>::type
hash_value( T const &)
{
return (hash_value)( static_cast<void*>( nullptr ) );
}
#endif

////////////////////////////////////////////////////////////
//
// Array types
//
////////////////////////////////////////////////////////////

//Forward declaration or internal hash functor, for array iteration
template<class T>
struct internal_hash_functor;

template<class T, std::size_t N>
inline std::size_t hash_value_dispatch( T const (&x)[ N ] )
{
std::size_t seed = 0;
for(std::size_t i = 0; i != N; ++i){
hash_combine_size_t(seed, internal_hash_functor<T>()(x[i]));
}
return seed;
}

template<class T, std::size_t N>
inline std::size_t hash_value_dispatch( T (&x)[ N ] )
{
std::size_t seed = 0;
for (std::size_t i = 0; i != N; ++i) {
hash_combine_size_t(seed, internal_hash_functor<T>()(x[i]));
}
return seed;
}

////////////////////////////////////////////////////////////
//
// Scalar types, calls proper overload
//
////////////////////////////////////////////////////////////
template <class T>
inline typename detail::enable_if_c<detail::is_scalar<T>::value, std::size_t>::type
hash_value_dispatch(const T &v)
{
return boost::intrusive::detail::hash_value(v);
}

//Internal "anonymous" hash functor, first selects between "built-in" scalar/array types
//and ADL-based lookup

template<class T>
struct internal_hash_functor
{
inline std::size_t operator()(T const& val) const
{
return ::boost::intrusive::detail::hash_value_dispatch(val);
}
};

} // namespace detail {
} // namespace intrusive {
} // namespace boost

#include <boost/intrusive/detail/config_end.hpp>

#endif // #ifndef BOOST_INTRUSIVE_HASH_HASH_HPP

60 changes: 11 additions & 49 deletions include/boost/intrusive/detail/hash_combine.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/////////////////////////////////////////////////////////////////////////////
//
// Copyright 2005-2014 Daniel James.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -13,9 +15,14 @@
// domain. The author hereby disclaims copyright to this source code.
//
// Copyright 2021 Ion Gaztanaga
// Refactored the original boost/container_hash/hash.hpp to avoid
// Refactored the original Boost ContainerHash library to avoid
// any heavy std header dependencies to just combine two hash
// values represented in a std::size_t type.
//
// See http://www.boost.org/libs/intrusive for documentation.
//
/////////////////////////////////////////////////////////////////////////////


#ifndef BOOST_INTRUSIVE_DETAIL_HASH_COMBINE_HPP
#define BOOST_INTRUSIVE_DETAIL_HASH_COMBINE_HPP
Expand All @@ -29,62 +36,17 @@
#endif

#include <boost/cstdint.hpp>

#if defined(_MSC_VER)
# include <stdlib.h>
# define BOOST_INTRUSIVE_HASH_ROTL32(x, r) _rotl(x,r)
#else
# define BOOST_INTRUSIVE_HASH_ROTL32(x, r) (x << r) | (x >> (32 - r))
#endif
#include "hash_mix.hpp"

namespace boost {
namespace intrusive {
namespace detail {

template <typename SizeT>
inline void hash_combine_size_t(SizeT& seed, SizeT value)
inline void hash_combine_size_t(std::size_t& seed, std::size_t value)
{
seed ^= value + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed = boost::intrusive::detail::hash_mix(seed + 0x9e3779b9 + value);
}

inline void hash_combine_size_t(boost::uint32_t& h1, boost::uint32_t k1)
{
const uint32_t c1 = 0xcc9e2d51;
const uint32_t c2 = 0x1b873593;

k1 *= c1;
k1 = BOOST_INTRUSIVE_HASH_ROTL32(k1,15);
k1 *= c2;

h1 ^= k1;
h1 = BOOST_INTRUSIVE_HASH_ROTL32(h1,13);
h1 = h1*5+0xe6546b64;
}


// Don't define 64-bit hash combine on platforms without 64 bit integers,
// and also not for 32-bit gcc as it warns about the 64-bit constant.
#if !defined(BOOST_NO_INT64_T) && \
!(defined(__GNUC__) && ULONG_MAX == 0xffffffff)
inline void hash_combine_size_t(boost::uint64_t& h, boost::uint64_t k)
{
const boost::uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
const int r = 47;

k *= m;
k ^= k >> r;
k *= m;

h ^= k;
h *= m;

// Completely arbitrary number, to prevent 0's
// from hashing to 0.
h += 0xe6546b64;
}

#endif // BOOST_NO_INT64_T

} //namespace detail {
} //namespace intrusive {
} //namespace boost {
Expand Down
Loading

0 comments on commit 6464d0e

Please sign in to comment.