Используйте boost :: hash_value для определения std :: hash в C ++ 11

Есть ли простой способ сделать следующее с C ++ 11 & Увеличение:

  • использовать стандартные определения std::hash по возможности <functional>
  • использование boost::hash_value определить std::hash в тех случаях, когда std::hash отсутствует, но boost::hash_value доступно в <boost/functional/hash.hpp>,

Например:

  • std::hash<std::vector<bool>> должен прийти из стандартной библиотеки,
  • std::hash<std::vector<unsigned>> должно быть реализовано с boost::hash_value,

2

Решение

Первая идея, которая приходит на ум, это использовать SFINAE и попробовать std::hash<> если возможно и иным образом использовать boost::hash_value(), как это:

#include <string>
#include <functional>
#include <type_traits>
#include <boost/functional/hash.hpp>

struct my_struct_0 {
std::string s;
};

template <typename T>
struct has_std_hash_subst { typedef void type; };

template <typename T, typename C = void>
struct has_std_hash : std::false_type {};

template <typename T>
struct has_std_hash<
T,
typename has_std_hash_subst<decltype( std::hash<T>()(T()) ) >::type
> : std::true_type {};

template <typename T>
static typename std::enable_if<has_std_hash<T>::value, size_t>::type
make_hash(const T &v)
{
return std::hash<T>()(v);
}

template <typename T>
static typename std::enable_if<(!has_std_hash<T>::value), size_t>::type
make_hash(const T &v)
{
return boost::hash_value(v);
}

int main()
{
make_hash(std::string("Hello, World!"));
make_hash(my_struct_0({ "Hello, World!" }));
}

К сожалению, всегда есть специализация по умолчанию std::hash это вызывает static_assert отказ. Это может быть не так с другими библиотеками, но это имеет место с GCC 4.7.2 (см. bits/functional_hash.h:60):

  /// Primary class template hash.
template<typename _Tp>
struct hash : public __hash_base<size_t, _Tp>
{
static_assert(sizeof(_Tp) < 0,
"std::hash is not specialized for this type");
size_t operator()(const _Tp&) const noexcept;
};

Таким образом, вышеуказанный подход SFINAE не работает — static_assert там есть шоу-стоппер. Таким образом, вы не можете определить, когда std::hash доступен.

Теперь это на самом деле не отвечает на ваш вопрос, но может пригодиться — можно сделать этот трюк с другой стороны — сначала проверьте реализацию Boost, а только затем вернитесь к std::hash<>, Рассмотрим приведенный ниже пример, который использует boost::hash_value() если это доступно (то есть для std::string а также my_struct_0) и иным образом использует std::hash<> (то есть для my_struct_1):

#include <string>
#include <functional>
#include <type_traits>
#include <boost/functional/hash.hpp>

struct my_struct_0 {
std::string s;
};

struct my_struct_1 {
std::string s;
};

namespace boost {
size_t hash_value(const my_struct_0 &v) {
return boost::hash_value(v.s);
}
}

namespace std {
template <>
struct hash<my_struct_1> {
size_t operator()(const my_struct_1 &v) const {
return std::hash<std::string>()(v.s);
}
};

}

template <typename T>
struct has_boost_hash_subst { typedef void type; };

template <typename T, typename C = void>
struct has_boost_hash : std::false_type {};

template <typename T>
struct has_boost_hash<
T,
typename has_boost_hash_subst<decltype(boost::hash_value(T()))>::type
> : std::true_type {};

template <typename T>
static typename std::enable_if<has_boost_hash<T>::value, size_t>::type
make_hash(const T &v)
{
size_t ret = boost::hash_value(v);
std::cout << "boost::hash_value(" << typeid(T).name()
<< ") = " << ret << '\n';
return ret;
}

template <typename T>
static typename std::enable_if<(!has_boost_hash<T>::value), size_t>::type
make_hash(const T &v)
{
size_t ret = std::hash<T>()(v);
std::cout << "std::hash(" << typeid(T).name()
<< ") = " << ret << '\n';
return ret;
}

int main()
{
make_hash(std::string("Hello, World!"));
make_hash(my_struct_0({ "Hello, World!" }));
make_hash(my_struct_1({ "Hello, World!" }));
}

Надеюсь, поможет.

ОБНОВЛЕНИЕ: Возможно, вы могли бы использовать описанный хак Вот как указано @ChristianRau и заставить первый подход SFINAE работать! Хотя это очень грязно 🙂

5

Другие решения

Мой ответ может быть неверным, но я постараюсь объяснить, почему я думаю, что ответ — нет.

Я не думаю что std::hash<T> а также boost:hash<T> могут использоваться взаимозаменяемо, поэтому я попытался скрыть создание объектов (даже если это не идеальное решение) и вернуть их результат, которым является size_t. Конечно, метод должен быть выбран во время компиляции, так что диспетчер функций — это то, что приходит мне в голову, пример кода:

template <typename T>
size_t createHash(const T& t, false_type)
{
return boost::hash<T>()(t);
}

template <typename T>
size_t createHash(const T& t, true_type)
{
return std::hash<T>()(t);
}

template<typename T>
size_t createHash(const T& t)
{
return createHash<T>(t, std::is_XXX<T>::type());
}int main()
{
vector<unsigned> v; v.push_back(1);
auto h1 = createHash(v);
cout << " hash: " << h1;
//hash<vector<unsigned> > h2;
}

Идея этого кода проста: если вы можете построить тип типа std::hash<T>выберите вторую реализацию, если нет — выберите первую.

Если выбрана первая реализация, код компилируется без проблем, вы можете проверить это с помощью fe. std::is_array<T>::type() в функции-обертке, что, конечно, не так, поэтому будет выбрана реализация boost :: hash. Однако, если вы используете черту, которая вернет true_t для vector<unsigned>вроде как std::is_class<T>::type() тогда компилятор сообщит «C ++ Standard не предоставляет …», что является результатом static_assert,

Чтобы это работало, нам нужно заставить компилятор возвращаться true_t если тип действительно конструируемый (он не терпит неудачу static_assert) и false_t если это не так. Однако я не думаю, что есть возможность сделать это.

1

По вопросам рекламы [email protected]