检查类型是否可以清洗

时间:2012-10-05 21:03:45

标签: c++ c++11 sfinae

我想创建一个类型特征,用于检查特定类型是否可以使用标准库的无序容器的默认实例化进行清理,因此它具有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.7 VC ++ 2012 为任何类型std::hash定义T,只有static_assert在非专业版中。但是不是有条件地编译它们(以及 clang 3.1 使用 gcc 4.7 libstdc ++ )而不是断言导致编译错误。这似乎是合理的,因为我认为static_assert不是由SFINAE处理的(对吧?),所以SFINAE解决方案似乎根本不可能。对于gcc 4.6而言,在一般static_assert模板中甚至没有std::hash,但只有未定义()运算符,情况更糟,在尝试使用它时导致链接器错误(这总是比编译错误更糟,我无法想象将链接器错误转换为编译器错误的任何方法。)

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

4 个答案:

答案 0 :(得分:8)

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

  1. 如果实例化失败并从重载集中删除相应的函数,则SFINAE应避免模板的任何实例化。
  2. static_assert()用于创建错误,例如,在模板实例化期间。
  3. 在我看来,1。显然胜过2.,即你的SFINAE应该有效。从两个独立的编译器供应商的外观不同意,不幸的是不是他们之间,而是与我。该标准似乎没有指定std::hash<T>的默认定义如何,并且似乎仅对std::hash<T>专用于类型T的情况施加约束。

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

答案 1 :(得分: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的部分作为第一个包含在内,那么这个肮脏的技巧应该有用......

答案 2 :(得分:4)

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

  

启用或禁用此模板的每种专业化(“中毒”)。对于库和用户均未为其提供启用的专业化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_hashable_v<int> << std::endl;
    std::cout << is_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已经为许多哈希提供了哈希。

答案 3 :(得分:0)

I hit this too. I tried a few workarounds and went with a whitelist filter for std::hash<>. the whitelist is not pleasant to maintain, but it is safe and it works.

I tried this on VS 2013, 2015, clang and 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";
}