Я хотел бы сделать черту типа для проверки того, является ли конкретный тип хешируемым, используя стандартные экземпляры неупорядоченных контейнеров стандартной библиотеки, таким образом, если он имеет действительную специализацию для 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 а также VC ++ 2012 определять std::hash
для любого типа T
, просто static_assert
в неспециализированной версии. Но вместо условной компиляции они (а также лязг 3.1 с помощью gcc 4.7«s libstdc ++) не соответствует утверждению, что приводит к ошибке компиляции. Это кажется разумным, так как я думаю, static_assert
SFINAE не обрабатывает (верно?), так что решение SFINAE кажется вообще невозможным. Это еще хуже для gcc 4.6
который даже не имеет static_assert
в общем std::hash
шаблон но просто не определяет его ()
оператор, приводящий к ошибке компоновщика при попытке его использовать (что всегда хуже, чем ошибка компиляции, и я не могу представить какой-либо способ превратить ошибку компоновщика в ошибку компилятора).
Таким образом, существует ли какой-либо стандартный и портативный способ определения такой черты типа, возвращаемой, если у типа есть действительный std::hash
специализация или, может быть, хотя бы для библиотек static_assert
в общем шаблоне (каким-то образом static_assert
ошибка в SFINAE не ошибка)?
Кажется, у нас есть два противоречивых требования:
static_assert()
предназначен для создания ошибки, например, во время создания шаблона.На мой взгляд, 1. явно козыри 2., т. Е. Ваш SFINAE должен работать. Судя по взглядам двух отдельных поставщиков компиляторов, они не согласны, к сожалению, не между собой, а со мной. Стандарт, похоже, не определяет, как определение по умолчанию std::hash<T>
выглядит и, кажется, накладывает ограничения только для случаев, когда std::hash<T>
специализируется на тип T
,
Я думаю, что ваши предложенные черты характера — разумная идея, и ее следует поддерживать. Однако, похоже, что стандарт не гарантирует, что он может быть реализован. Возможно, стоит обсудить это с поставщиками компиляторов и / или подать отчет о дефектах для стандарта: насколько я могу судить, текущая спецификация не дает четкого руководства, что должно произойти. … и если спецификация в настоящее время предписывает, что черты типа, как указано выше, не выполняются, это может быть ошибкой проектирования, которую необходимо исправить.
Вот ОЧЕНЬ грязное решение вашей проблемы: оно работает для 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
включен как первый включающий этот подвох должен работать …
Начиная с C ++ 17 теперь можно делать это более элегантно.
От cppreference о std :: hash:
Каждая специализация этого шаблона либо включена («незапятнана»), либо отключена («отравлена»). Для каждого типа Key, для которого ни библиотека, ни пользователь не предоставляют включенную специализацию std :: hash, эта специализация существует и отключена. Отключенные специализации не удовлетворяют Hash, не удовлетворяют FunctionObject, и std :: is_default_constructible_v, std :: is_copy_constructible_v, std :: is_move_constructible_v, std :: is_copy_assignable_v, std :: is_move_assignable_v все ложны. Другими словами, они существуют, но не могут быть использованы.
Это означало, что STL должен был удалить static_assert в C ++ 17. Вот рабочее решение с Clang-6.0.0 -std = c ++ 17:
#include <functional>
#include <ios>
#include <iostream>
#include <type_traits>
template <typename T, typename = std::void_t<>>
struct is_std_hashable : std::false_type { };
template <typename T>
struct is_std_hashable<T, std::void_t<decltype(std::declval<std::hash<T>>()(std::declval<T>()))>> : std::true_type { };
template <typename T>
constexpr bool is_std_hashable_v = is_std_hashable<T>::value;
struct NotHashable {};
int main()
{
std::cout << std::boolalpha;
std::cout << is_hashable_v<int> << std::endl;
std::cout << is_hashable_v<NotHashable> << std::endl;
return 0;
}
Это может пригодиться, например, когда вы используете повышение :: hash_combine или же повышение :: hash_range. Если вы включаете заголовок, содержащий следующий пример кода, вам больше не нужно определять бэши для определенных типов.
#include <boost/functional/hash_fwd.hpp>
template <typename T, typename = std::void_t<>>
struct is_boost_hashable : std::false_type { };
template <typename T>
struct is_boost_hashable<T, std::void_t<decltype(boost::hash_value(std::declval<T>()))>> : std::true_type { };
template <typename T>
constexpr bool is_boost_hashable_v = is_boost_hashable<T>::value;
namespace boost
{
template <typename T>
auto hash_value(const T &arg) -> std::enable_if_t<is_std_hashable_v<T> &&
!is_boost_hashable_v<T>, std::size_t>
{
return std::hash<T>{}(arg);
}
}
Обратите внимание на is_boost_hashable_v, это необходимо, чтобы избежать двусмысленности, так как boost уже предоставляет хеши для большого количества хешей.
Я ударил это тоже. Я попробовал несколько обходных путей и пошел с фильтром белого списка для 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";
}