Есть ли простой способ сделать следующее с 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
,Первая идея, которая приходит на ум, это использовать 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 работать! Хотя это очень грязно 🙂
Мой ответ может быть неверным, но я постараюсь объяснить, почему я думаю, что ответ — нет.
Я не думаю что 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
если это не так. Однако я не думаю, что есть возможность сделать это.