Проверьте, есть ли тип хешируемый - программирование
Подтвердить что ты не робот

Проверьте, есть ли тип хешируемый

Я хотел бы сделать черту типа для проверки того, является ли конкретный тип хешируемым, используя стандартные экземпляры стандартных неупорядоченных контейнеров библиотеки, поэтому, если он имеет действительную специализацию для 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 и VС++ 2012 определяют std::hash для любого типа T, просто static_assert ing в неспециализированной версии. Но вместо компиляции условно они (а также clang 3.1 с использованием gcc 4.7 libstdС++) вызывают утверждение, приводящее к ошибке компиляции. Это кажется разумным, так как я думаю, что static_assert не обрабатывается SFINAE (правильно?), Поэтому решение SFINAE кажется совсем не возможным. Это еще хуже для gcc 4.6, который даже не имеет static_assert в общем шаблоне std::hash, но только не определяет его оператор (), что приводит к ошибке компоновщика при попытке (что всегда хуже, чем ошибка компиляции, и я не могу представить, каким образом можно преобразовать ошибку компоновщика в ошибку компилятора).

Итак, существует ли стандартно-совместимый и переносимый способ определения возвращаемого типа, если тип имеет допустимую специализацию std::hash или, возможно, по крайней мере для библиотек static_assert ing в общем шаблоне (каким-то образом преобразуя static_assert ошибка в ошибке без SFINAE)?

4b9b3361

Ответ 1

Кажется, у нас есть два противоречивых требования:

  • SFINAE предназначена для предотвращения создания экземпляра шаблона, если инстанцирование может завершиться неудачей и удалить соответствующую функцию из набора перегрузки.
  • static_assert() предназначен для создания ошибки, например, во время создания шаблона.

На мой взгляд, 1. ясно козыри 2., т.е. ваш SFINAE должен работать. Из взглядов двух отдельных поставщиков компилятора не согласны, к сожалению, не между собой, а со мной. В стандарте не указано, как выглядит определение по умолчанию std::hash<T> и, кажется, налагает ограничения только на случаи, когда std::hash<T> специализирован для типа T.

Я думаю, что ваши предлагаемые черты характера - разумная идея, и ее нужно поддерживать. Однако, похоже, стандарт не гарантирует, что он может быть реализован. Возможно, стоит довести это до сведения поставщиков компилятора и/или представить отчет о дефектах для стандарта: текущая спецификация не дает четких указаний о том, что должно произойти, насколько я могу судить.... и если в спецификации в настоящее время указано, что признаки типа, как указано выше, могут быть ошибкой дизайна, которая должна быть исправлена.

Ответ 2

Вот очень грязное решение вашей проблемы: оно работает для GCC 4.7 (а не 4.6 из-за отсутствия функции С++ 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. Вам нужно будет его модифицировать для VС++, в частности, для исправления для _Hash_impl::hash(), поскольку это деталь реализации.

Если вы убедитесь, что раздел, обозначенный как is_hashable.h, включен как первый, включите этот грязный трюк, который должен работать...

Ответ 3

Я тоже ударил. Я попробовал несколько обходных решений и пошел с фильтром белого списка для std::hash<>. белый список не нравится поддерживать, но он безопасен и работает.

Я пробовал это на VS 2013, 2015, clang и gcc.

#include <iostream>
#include <type_traits>

// based on Walter Brown 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";
}