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の両方が、特殊化されていないバージョンで ing するだけでstd::hash、任意の typeを定義することを学びました。しかし、条件付きでコンパイルする代わりに (さらにgcc 4.7libstdc++を使用して3.1 をclangします)、アサーションに失敗し、コンパイル エラーが発生します。s は SFINAE によって処理されないと思うので (そうですか?)、これは合理的に思えます。一般的なテンプレートにさえないのに、そのテンプレートを定義していないのさらに悪いことですTstatic_assertstatic_assertgcc 4.6static_assertstd::hash()これを使用しようとするとリンカー エラーが発生します (これは常にコンパイル エラーよりも悪く、リンカー エラーをコンパイラ エラーに変換する方法は想像できません)。

std::hashしたがって、型に有効な特殊化がある場合、または少なくともライブラリstatic_assertが一般的なテンプレートで ing (何らかの方法でstatic_assertエラーを SFINAE 非エラーに変換する)の場合に返すような型特性を定義する、標準に準拠した移植可能な方法はありますか?

4

4 に答える 4

8

相反する 2 つの要件があるようです。

  1. SFINAE は、インスタンス化が失敗し、対応する関数をオーバーロード セットから削除する可能性がある場合に、テンプレートのインスタンス化を回避することを目的としています。
  2. static_assert()たとえば、テンプレートのインスタンス化中にエラーを作成するためのものです。

私の考えでは、1. は明らかに 2. に勝っています。つまり、SFINAE は機能するはずです。残念ながら、2 つの異なるコンパイラ ベンダーの意見は一致していません。標準は、 のデフォルト定義がどのように見えるかを指定していないようであり、 が type に特化されてstd::hash<T>いる場合にのみ制約を課しているようです。std::hash<T>T

提案された型特性は合理的なアイデアであり、サポートされるべきだと思います。ただし、標準は実装できることを保証していないようです。このことをコンパイラ ベンダーに報告したり、標準の欠陥レポートを提出したりする価値があるかもしれません。現在の仕様では、私が知る限り、何が起こるべきかについて明確なガイダンスを提供していません。...そして、仕様が現在、上記のような型特性が失敗することを義務付けている場合、修正が必要な設計エラーである可能性があります。

于 2012-10-05T21:55:12.420 に答える
4

ここにあなたの問題に対する非常に汚い解決策があります:GCC 4.7で動作します(C ++ 11機能がないため、4.6では動作しません:マングリングオーバーロード)

// 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 に答える