2012-10-05 11 views
16

Me gustaría hacer un rasgo de tipo para verificar si un tipo particular es hashable utilizando las instancias predeterminadas de los contenedores no ordenados de la biblioteca estándar, por lo tanto, si tiene una especialización válida para std::hash. Creo que esta sería una característica muy útil (por ejemplo, para usar std::set como failsafe para std::unordered_set en código genérico). Así que, no se define pensando std::hash para cada tipo, empecé a hacer la siguiente solución SFINAE: (. Perdona mis modestos SFINAE-habilidades si esto no es la mejor solución o incluso erróneos)Compruebe si el tipo es hashable

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> {}; 

Pero entonces aprendí que gcc 4.7 y VC++ 2012 definen std::hash para cualquier tipo T, solo static_assert ing en la versión no especializada. Pero en lugar de compilar condicionalmente ellos (y también sonido metálico 3.1 usando gcc 4.7 's libstdC++) fallan la afirmación que resulta en un error de compilación. Esto parece razonable ya que creo que static_assert s no son manejados por SFINAE (¿verdad?), Por lo que una solución SFINAE parece no ser posible. Es aún peor para gcc 4.6 que ni siquiera tiene static_assert en la plantilla general std::hash pero no define como su operador , lo que provoca un error del enlazador al intentar usarlo (que siempre es peor que un error de compilación y No puedo imaginar ninguna forma de transformar un error de enlazador en un error de compilación).

Entonces, ¿hay alguna manera que cumplan el estándar y portátil para definir un rasgo de este tipo regresar si un tipo tiene un std::hash especialización válida, o tal vez por lo menos para las bibliotecas static_assert ING en la plantilla general (de alguna manera transformar el error static_assert en un SFINAE sin errores)?

+0

EDIT: Ok, mi declaración acerca * VC++ * compilando que era de alguna distinta versión más antigua, de hecho * VC++ * se comporta como * gcc *, asfixia en el 'static_assert' –

+0

se parece a la gente del CCG son conscientes del problema mientras tanto. Se dice que Gcc 4.8 ya no tiene esta afirmación estática, pero que consideran implementar una implementación estándar en el futuro, algo similar al hash de Boost, donde la implementación es mejorada por ADL. – Ichthyo

+0

https://gcc.gnu.org/ml/libstdc++/2013-03/msg00029.html – Ichthyo

Respuesta

6

Parece que tenemos dos exigencias contradictorias:

  1. SFINAE el objetivo es evitar cualquier instanciación de una plantilla si la creación de instancias puede fallar y quitar la función correspondiente del conjunto de sobrecarga.
  2. static_assert() está destinado a crear un error, por ejemplo, durante la creación de instancias de una plantilla.

En mi opinión, 1. triunfa claramente 2., es decir, su SFINAE debería funcionar. Desde el punto de vista de dos vendedores de compiladores independientes no están de acuerdo, lamentablemente no entre ellos sino conmigo. El estándar no parece especificar cómo se ve la definición predeterminada de std::hash<T> y parece imponer restricciones solo para los casos en que std::hash<T> está especializado para un tipo T.

Creo que los rasgos de tipo propuestos son una idea razonable y deben ser compatibles. Sin embargo, parece que el estándar no garantiza que pueda implementarse. Puede valer la pena mencionar esto con los proveedores del compilador y/o presentar un informe de defectos para el estándar: la especificación actual no da una guía clara de lo que debería suceder, hasta donde yo sé. ... y si la especificación actualmente exige que un tipo de caracteres como el anterior falla, puede ser un error de diseño que debe corregirse.

+3

_Desde el aspecto de dos vendedores independientes de compiladores no está de acuerdo, desafortunadamente no entre ellos sino con me_ - lol – sehe

+0

En su opinión 1. podría triunfar 2., pero el estándar no está de acuerdo, desafortunadamente. Los compiladores son completamente correctos al rechazar esto. – fgp

+0

@fgp ¿Podría proporcionar algunas citas del estándar que prueban este desacuerdo? –

3

Aquí hay una solución MUY sucia a su problema: Funciona para GCC 4.7 (y no 4.6, debido a la falta de C++ 11 característica: mangling sobrecarga)

// 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"; 
} 

Y la salida es:

1, 0, 1

Básicamente "secuestran" el hash símbolo e inyecte un ayudante typedef en él. Tendrá que modificarlo para VC++, en particular, la corrección para _Hash_impl::hash() ya que es un detalle de implementación.

Si se asegura de que la sección etiquetada como is_hashable.h se incluye como primer incluir este truco sucio debería funcionar ...

+0

¡Ingenioso y al mismo tiempo aterrador! Me encanta, siempre y cuando * nunca * aparezca en el código con el que tengo que trabajar ;-) – fgp

+0

@fgp Tanto para la encapsulación :) – enobayram

0

llegué a esto también. Intenté algunas soluciones y fui con un filtro de lista blanca para std::hash<>. la lista blanca no es agradable de mantener, pero es segura y funciona.

Probé esto en VS 2013, 2015, clang y 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

Pero ¿por qué la complicada solución con 'filtered_hash' en primer lugar si necesita para enumerar todas las especializaciones de todos modos. ¿Por qué no simplemente especializar 'is_hashable' para todos los tipos? –

+0

Proporciona flexibilidad en el uso. Puedo usar filtered_hash en lugar de std :: hash o usar is_hashable para proteger el uso de std :: hash. –

Cuestiones relacionadas