21

我想使用标准库的无序容器的默认实例创建一个类型特征来检查特定类型是否可散列,因此它是否具有std::hash. 我认为这将是一个非常有用的功能(例如,在通用代码中std::set用作故障保护)。std::unordered_set因此,我认为std::hash没有为每种类型定义,开始制作以下 SFINAE 解决方案:

template<typename T> std::true_type hashable_helper(
    const T&, const typename std::hash<T>::argument_type* = nullptr);

template<typename T> std::false_type hashable_helper(...);

//It won't let me derive from decltype directly, why?
template<typename T> struct is_hashable 
    : std::is_same<decltype(hashable_helper<T>(std::declval<T>())),
                   std::true_type> {};

(如果这不是最好的解决方案甚至是错误的,请原谅我谦虚的 SFINAE 能力。)

但后来我了解到,gcc 4.7VC++ 2012都定义std::hash了任何类型T,只是static_assert在非专业版本中。但是,它们不是有条件地编译(以及使用gcc 4.7libstdc++的clang 3.1)使断言失败,从而导致编译错误。这似乎是合理的,因为我认为s 不是由 SFINAE 处理的(对吗?),所以 SFINAE 解决方案似乎根本不可能。更糟糕的是,在通用模板中甚至没有 a但只是没有定义static_assertgcc 4.6static_assertstd::hash()运算符,在尝试使用它时导致链接器错误(这总是比编译错误更糟糕,我无法想象有任何方法可以将链接器错误转换为编译器错误)。

那么,如果类型具有有效的特std::hash化,或者至少对于static_assert通用模板中的库(以某种方式将static_assert错误转换为 SFINAE 非错误),是否有任何符合标准且可移植的方法来定义返回的这种类型特征?

4

4 回答 4

11

从 C++17 开始,现在可以以更优雅的方式执行此操作。来自关于 std::hash 的cppreference :

此模板的每个特化要么启用(“未污染”),要么禁用(“中毒”)。对于库和用户都没有为其提供启用的特化 std::hash 的每个类型 Key,该特化存在并且被禁用。禁用的特化不满足 Hash,不满足 FunctionObject,并且 std::is_default_constructible_v、std::is_copy_constructible_v、std::is_move_constructible_v、std::is_copy_assignable_v、std::is_move_assignable_v 都是假的。换句话说,它们存在,但不能使用。

这意味着 STL 必须在 C++17 中删除 static_assert。这是使用“Clang-6.0.0 -std=c++17”的有效解决方案:

#include <functional>
#include <ios>
#include <iostream>
#include <type_traits>

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

template <typename T>
struct is_std_hashable<T, std::void_t<decltype(std::declval<std::hash<T>>()(std::declval<T>()))>> : std::true_type { };

template <typename T>
constexpr bool is_std_hashable_v = is_std_hashable<T>::value; 

struct NotHashable {};

int main()
{
    std::cout << std::boolalpha;
    std::cout << is_std_hashable_v<int> << std::endl;
    std::cout << is_std_hashable_v<NotHashable> << std::endl;
    return 0;
}

例如,当您使用boost::hash_combineboost::hash_range时,这可能会派上用场。如果包含包含以下代码示例的标头,则不再需要为特定类型定义提升哈希。

#include <boost/functional/hash_fwd.hpp>

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

template <typename T>
struct is_boost_hashable<T, std::void_t<decltype(boost::hash_value(std::declval<T>()))>> : std::true_type { };

template <typename T>
constexpr bool is_boost_hashable_v = is_boost_hashable<T>::value;  

namespace boost
{
    template <typename T>
    auto hash_value(const T &arg) -> std::enable_if_t<is_std_hashable_v<T> &&
                                                      !is_boost_hashable_v<T>, std::size_t>
    {
        return std::hash<T>{}(arg);
    }
}

注意is_boost_hashable_v,这是避免歧义所必需的,因为 boost 已经为许多散列提供了散列。

于 2018-08-19T08:24:22.790 回答
8

似乎我们有两个相互矛盾的要求:

  1. 如果实例化可能失败并从重载集中删除相应的函数,SFINAE 旨在避免模板的任何实例化。
  2. static_assert()旨在创建错误,例如,在模板的实例化期间。

在我看来,1. 显然胜过 2.,即您的 SFINAE 应该可以工作。从两个独立的编译器供应商的外观来看,不幸的是不是在他们之间而是在我之间。该标准似乎没有指定默认定义的std::hash<T>外观,并且似乎仅对std::hash<T>专门针对 type的情况施加约束T

我认为您提出的类型特征是一个合理的想法,应该得到支持。但是,该标准似乎并不能保证它可以实施。可能值得向编译器供应商提出这个问题和/或提交标准的缺陷报告:据我所知,当前的规范没有给出明确的指导应该发生什么。...如果规范当前要求上述类型特征失败,则可能是需要纠正的设计错误。

于 2012-10-05T21:55:12.420 回答
4

这是您的问题的一个非常肮脏的解决方案:它适用于 GCC 4.7(而不是 4.6,由于缺少 C++11 功能:重载重载)

// is_hashable.h
namespace std {
    template <class T>
    struct hash {
        typedef int not_hashable;
    };

}

#define hash hash_
#define _Hash_impl _Hash_impl_
#include<functional>
#undef hash
#undef _Hash_impl

namespace std {
    struct _Hash_impl: public std::_Hash_impl_{
        template <typename... Args>
            static auto hash(Args&&... args) 
                -> decltype(hash_(std::forward<Args>(args)...)) {
             return hash_(std::forward<Args>(args)...);
        }
    };
    template<> struct hash<bool>: public hash_<bool> {};
    // do this exhaustively for all the hashed standard types listed in:
    // http://en.cppreference.com/w/cpp/utility/hash
}

template <typename T>
class is_hashable
{
    typedef char one;
    typedef long two;

    template <typename C> static one test( typename std::hash<C>::not_hashable ) ;
    template <typename C> static two test(...);


public:
    enum { value = sizeof(test<T>(0)) == sizeof(long) };
};


// main.cpp
// #include "is_hashable.h"
#include<iostream>
#include<unordered_set>

class C {};

class D {
public:
    bool operator== (const D & other) const {return true;}
};

namespace std {
    template <> struct hash<D> {
        size_t operator()(const D & d) const { return 0;}
    };
}

int main() {
    std::unordered_set<bool> boolset; 
    boolset.insert(true);
    std::unordered_set<D> dset; 
    dset.insert(D());// so the hash table functions
    std::cout<<is_hashable<bool>::value<<", ";
    std::cout<<is_hashable<C>::value << ", ";
    std::cout<<is_hashable<D>::value << "\n";
}

输出是:

1, 0, 1

我们基本上“劫持”了哈希符号并typedef在其中注入了一些帮助程序。您需要为 VC++ 修改它,特别是修复,_Hash_impl::hash()因为它是一个实现细节。

如果您确保将标记为的部分is_hashable.h作为第一个包含在内,那么这个肮脏的技巧应该可以工作......

于 2012-10-05T23:37:51.970 回答
0

我也打这个。我尝试了一些解决方法,并为std::hash<>. 白名单维护起来并不愉快,但它安全且有效。

我在 VS 2013、2015、clang 和 gcc 上试过这个。

#include <iostream>
#include <type_traits>

// based on Walter Brown's void_t proposal
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3911.pdf
namespace detail {
    template<class... TN> struct void_t {typedef void type;};
}
template<class... TN>
struct void_t {typedef typename detail::void_t<TN...>::type type;};

// extensible whitelist for std::hash<>
template <class T, typename = void> 
struct filtered_hash;
template <class T> 
struct filtered_hash<T, 
    typename std::enable_if<std::is_enum<T>::value>::type> 
    : std::hash<T> {
};
template <class T> 
struct filtered_hash<T, 
    typename std::enable_if<std::is_integral<T>::value>::type> 
    : std::hash<T> {
};
template <class T> 
struct filtered_hash<T, 
    typename std::enable_if<std::is_pointer<T>::value>::type> 
    : std::hash<T> {
};

template<typename, typename = void>
struct is_hashable
    : std::false_type {};

template<typename T>
struct is_hashable<T, 
    typename void_t<
        typename filtered_hash<T>::result_type, 
        typename filtered_hash<T>::argument_type, 
        typename std::result_of<filtered_hash<T>(T)>::type>::type>
    : std::true_type {};

// try it out..
struct NotHashable {};

static_assert(is_hashable<int>::value, "int not hashable?!");
static_assert(!is_hashable<NotHashable>::value, "NotHashable hashable?!");

int main()
{
    std::cout << "Hello, world!\n";
}
于 2016-02-03T02:55:01.390 回答