Проверьте, есть ли тип хешируемый
Я хотел бы сделать черту типа для проверки того, является ли конкретный тип хешируемым, используя стандартные экземпляры стандартных неупорядоченных контейнеров библиотеки, поэтому, если он имеет действительную специализацию для 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)?
Ответы
Ответ 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";
}