2012-10-05 61 views
16

我想用类型trait来检查一个特定的类型是否可以使用标准库的无序容器的缺省实例化,因此如果它具有std::hash的有效特化。我认为这将是一个非常有用的功能(例如,在通用代码中使用std::set作为std::unordered_set的失败保护)。所以我想std::hash没有为每种类型的定义,开始做以下SFINAE解决方案:(请原谅我的微薄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> {}; 

但然后我知道,gcc 4.7VC++ 2012定义std::hash任何类型T,只是static_assert在非专业版本。但不是有条件地编译它们(还有铛3.1使用gcc 4.7libstdC++)导致编译错误的断言失败。这似乎是合理的,因为我认为static_assert s不是由SFINAE处理的(对吧?),所以SFINAE解决方案似乎根本不可能。它更糟糕的是gcc 4.6甚至在一般std::hash模板中甚至没有static_assert模板,但是没有定义它的()运算符,导致尝试使用它时出现链接器错误(这总是比编译错误更糟糕,我无法想象任何方式将链接器错误转换为编译器错误)。

那么,有没有符合标准的和可移植的方式来定义这样的类型特征返回如果一个类型有一个有效的std::hash专业化,或许至少荷兰国际集团在通用模板(在某种程度上转化static_assert错误到库static_assert一个SFINAE非错误)?

+0

编辑:好吧,我对声明* VC++ *编译它是由一些不同的旧版本,其实* VC++ *行为像* GCC *,窒息了'static_assert' –

+0

看起来像海湾合作委员会的人都知道的问题。据说Gcc 4.8没有这个静态断言,但他们考虑将来推出一个标准的实现,有点类似于Boost hash,其中实现被ADL拾取。 – Ichthyo

+0

https://gcc.gnu.org/ml/libstdc++/2013-03/msg00029.html – Ichthyo

回答

6

看来我们有两个相互矛盾的要求:

  1. SFINAE是为了避免模板的任何实例,如果实例化可能会失败,并从重载集合中删除相应的功能。
  2. static_assert()意味着例如在模板的实例化期间产生错误。

在我看来,1.明显胜过2.,即你的SFINAE应该工作。从两个独立的编译器供应商看起来不同意,不幸的是他们不在自己之间,而是与我在一起。该标准似乎没有说明std::hash<T>的默认定义是什么样子,并且似乎仅对std::hash<T>专用于T类型的情况施加了约束。

我认为你提出的类型特征是一个合理的想法,它应该被支持。但是,标准似乎并不能保证它可以实施。就编译器厂商和/或提交缺陷报告来说,这可能是值得的。就我所知,目前的规范并没有给出明确的指导意见。 ...如果规范目前规定如上所述的类型特征不合格,则可能是需要纠正的设计错误。

+3

_From看起来两个独立的编译器供应商不同意,不幸的是不在他们自己之间,但与我__ lol – sehe

+0

你的想法1.可能王牌2.,但标准不同意,不幸的是。编译器因此完全正确地拒绝这一点。 – fgp

+0

@fgp你能否提供一些来自标准的引用来证明这种不一致? –

3

这是一个非常脏的解决方案,您的问题:它适用于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的部分为第一包括此使坏应该工作...

+0

巧妙而又可怕!我喜欢它,只要它*永远不会*出现在我必须使用的代码中;-) – fgp

+0

@fgp封装太多了:) – enobayram

0

我打了这一点。我尝试了一些解决方法,并使用白名单过滤器for 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"; 
} 
+0

但是,为什么如果您需要首先使用'filtered_hash'的复杂解决方法无论如何要列出所有的专业化。为什么不只为所有类型专门设置'is_hashable'呢? –

+0

它提供了使用的灵活性。我可以使用filtered_hash代替std :: hash或使用is_hashable来保护std :: hash的使用。 –