2012-10-05 100 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的使用。 –