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

Есть ли простой способ сделать следующее с С++ 11 и Boost:

  • используйте стандартные определения 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.

person Jukka Suomela    schedule 13.02.2013    source источник
comment
Связано: stackoverflow.com/q/12753997/743214   -  person Christian Rau    schedule 15.02.2013


Ответы (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 работает! Хотя очень грязно :)

person Community    schedule 14.02.2013

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

Я не думаю, что 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>, например fe. std::is_class<T>::type(), то компилятор сообщит "Стандарт C++ не предоставляет...", что является результатом ошибки static_assert.

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

person Marcin Deptuła    schedule 13.02.2013