【问题标题】:Use boost::hash_value to define std::hash in C++11使用 boost::hash_value 在 C++11 中定义 std::hash
【发布时间】:2013-01-29 14:33:53
【问题描述】:

是否有一种简单的方法可以使用 C++11 和 Boost 执行以下操作:

  • 使用std::hash 的标准定义,只要可从<functional> 获得
  • std::hash 缺失但boost::hash_value<boost/functional/hash.hpp> 中可用的情况下,使用boost::hash_value 定义std::hash

例如:

  • std::hash<std::vector<bool>> 应该来自标准库,
  • std::hash<std::vector<unsigned>> 应该使用 boost::hash_value 来实现。

【问题讨论】:

标签: c++ c++11 boost hash stdhash


【解决方案1】:

想到的第一个想法是使用 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&lt;&gt;。考虑以下示例,如果boost::hash_value() 可用(即std::stringmy_struct_0),则使用std::hash&lt;&gt;(即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 所指出的here 所描述的hack,并使第一个SFINAE 方法起作用!虽然很脏:)

【讨论】:

    【解决方案2】:

    我的答案可能不正确,但我会尝试解释为什么我认为答案是否定的。

    我不认为std::hash&lt;T&gt;boost:hash&lt;T&gt; 可以互换使用,所以我尝试隐藏对象创建(即使这不是完美的解决方案),并返回它们的结果,即 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&lt;T&gt;的类型,则选择第二个实现,否则 - 选择第一个。

    如果选择第一个实现,代码编译没有问题,您可以使用 fe 进行检查。 std::is_array&lt;T&gt;::type() 在一个包装函数中,这当然不是真的,所以会选择 boost::hash 实现。但是,如果您使用一个特征,它将为 vector&lt;unsigned&gt; 返回 true_t,例如 fe。 std::is_class&lt;T&gt;::type() 然后编译器将报告“C++ 标准不提供...”,这是 static_assert 的结果。

    为此,我们需要强制编译器返回true_t,如果一个类型真的是可构造的(它不会失败static_assert),如果不是,false_t。但是,我不认为有可能这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-08
      • 2019-04-28
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      相关资源
      最近更新 更多