Program Listing for File hash.hpp

Return to documentation for file (SeQuant/core/hash.hpp)

//
// Created by Eduard Valeyev on 2019-01-29.
//

#ifndef SEQUANT_HASH_HPP
#define SEQUANT_HASH_HPP

#ifdef SEQUANT_USE_SYSTEM_BOOST_HASH
#include <boost/version.hpp>
#define SEQUANT_BOOST_VERSION BOOST_VERSION
#include <boost/container_hash/hash.hpp>
namespace sequant_boost = boost;
#else
#include <SeQuant/external/boost/container_hash/hash.hpp>
#endif

#if SEQUANT_BOOST_VERSION < 108100
#error "SeQuant requires Boost 1.81 or later for hashing"
#endif

#include <SeQuant/core/meta.hpp>

namespace sequant {

namespace hash {

enum class Impl { BoostPre181 = 1, Boost181OrLater = 2 };

}  // namespace hash

constexpr hash::Impl hash_version() { return hash::Impl::Boost181OrLater; }

namespace detail {
template <typename T, typename Enabler = void>
struct has_hash_value_member_fn_helper : public std::false_type {};
template <typename T>
struct has_hash_value_member_fn_helper<
    T, std::void_t<decltype(std::declval<const T&>().hash_value())>>
    : public std::true_type {};
}  // namespace detail

template <typename T>
static constexpr bool has_hash_value_member_fn_v =
    detail::has_hash_value_member_fn_helper<T>::value;

namespace detail {

template <typename T, typename = std::void_t<>>
struct has_boost_hash_value : std::false_type {};

template <typename T>
struct has_boost_hash_value<T, std::void_t<decltype(sequant_boost::hash_value(
                                   std::declval<const T&>()))>>
    : std::true_type {};

template <typename T, typename = std::void_t<>>
struct has_hash_value : std::false_type {};

template <typename T>
struct has_hash_value<
    T, std::void_t<decltype(hash_value(std::declval<const T&>()))>>
    : std::true_type {};

}  // namespace detail

template <typename T>
constexpr bool has_boost_hash_value_v =
    detail::has_boost_hash_value<const T&>::value;

template <typename T>
constexpr bool has_hash_value_v = detail::has_hash_value<T>::value;

// hash_value specialization for types that have a hash_value member function
template <typename T,
          std::enable_if_t<has_hash_value_member_fn_v<T>, short> = 0>
auto hash_value(const T& obj) {
  return obj.hash_value();
}

// hash_value specialization that don't have a hash_value member function but
// have an applicable boost::hash_value function
template <typename T, std::enable_if_t<!has_hash_value_member_fn_v<T> &&
                                           has_boost_hash_value_v<T>,
                                       int> = 0>
auto hash_value(const T& obj) {
  return sequant_boost::hash_value(obj);
}

// clang-format off
// rationale:
// boost::hash_combine is busted ... it dispatches to one of 3 implementations (all line numbers refer to boost 1.72.0):
// - templated: container_hash/hash.hpp:309
// - 32-bit: container_hash/hash.hpp:315
// - 64-bit: container_hash/hash.hpp:336
// n.b. The latter is disabled if int64_t is not available or 32-bit gcc is used
// Somehow Macos clang dispatches to the first version, but everywhere else (Linux) dispatch ends up to the last one.
// This skips the first version. Since boost::hash_range is implemented in terms of boost::hash_combine must reimplement
// that also.
// clang-format on

namespace hash {

template <typename T, typename Enabler = void>
struct _;

template <class T>
inline void combine(std::size_t& seed, T const& v) {
  _<T> hasher;

  sequant_boost::hash_combine(seed, hasher(v));

  //  assert(seed == seed_ref);
}

template <class It>
inline std::size_t range(It first, It last) {
  //  const std::size_t seed_ref = boost::hash_range(first, last);
  std::size_t seed = 0;

  for (; first != last; ++first) {
    hash::combine(seed, *first);
  }

  //  assert(seed == seed_ref);
  return seed;
}

template <class It>
inline void range(std::size_t& seed, It first, It last) {
  //  std::size_t seed_ref = seed;
  //  boost::hash_range(seed_ref, first, last);
  for (; first != last; ++first) {
    hash::combine(seed, *first);
  }
  //  assert(seed == seed_ref);
}

template <typename It>
std::size_t hash_range(It begin, It end) {
  if (begin != end) {
    std::size_t seed = hash_value(*begin);
    sequant_boost::hash_range(seed, begin + 1, end);
    return seed;
  } else
    return sequant_boost::hash_range(begin, end);
}

template <typename It>
void hash_range(size_t& seed, It begin, It end) {
  sequant_boost::hash_range(seed, begin, end);
}

template <typename T>
struct _<T, std::enable_if_t<!(has_hash_value_v<T>)&&meta::is_range_v<T>>> {
  std::size_t operator()(T const& v) const {
    using std::begin;
    using std::end;
    return range(begin(v), end(v));
  }
};

template <typename T>
struct _<T, std::enable_if_t<!(!(has_hash_value_v<T>)&&meta::is_range_v<T>)>> {
  std::size_t operator()(T const& v) const { return hash_value(v); }
};

template <typename T>
auto value(const T& obj) {
  return _<T>{}(obj);
}

}  // namespace hash

}  // namespace sequant

#endif  // SEQUANT_HASH_HPP