2012-05-05 14 views
5

Tengo una clase de plantilla Baz que contiene una clase anidada Sub. Me gustaría definir una función hash para esta subclase al especializar std :: hash. Sin embargo, parece que no funciona.Especialización std :: hash para clase anidada en una clase de plantilla

#include <functional> 

struct Foo { 
    struct Sub { 
    }; 
}; 

template <class T> 
struct Bar { 
}; 

template <class T> 
struct Baz { 
    struct Sub { 
     int x; 
    }; 
}; 

// declare hash for Foo::Sub - all right 
namespace std { 
    template <> 
    struct hash<Foo::Sub>; 
} 

// declare hash for Bar<T> - all right 
namespace std { 
    template <class T> 
    struct hash< Bar<T> >; 
} 

// declare hash function for Baz<T>::Sub - doesn't work! 
namespace std { 
    template <class T> 
    struct hash< Baz<T>::Sub >; 
} 

// Adding typename produces a different error. 
namespace std { 
    template <class T> 
    struct hash< typename Baz<T>::Sub >; 
} 

gcc 4.5.3 se queja:

$ g++ -std=c++0x -c hash.cpp 
hash.cpp:34:30: error: type/value mismatch at argument 1 in template parameter list for ‘template<class _Tp> struct std::hash’ 
hash.cpp:34:30: error: expected a type, got ‘Baz<T>::Sub’ 
hash.cpp:40:12: error: template parameters not used in partial specialization: 
hash.cpp:40:12: error:   ‘T’ 

ACTUALIZACIÓN

Lo que realmente estoy tratando de hacer es poner en práctica un contenedor que soporta estables referencias (no en el C++ sentido) a los elementos dentro de él. Quiero permitir al usuario insertar estas referencias en std::unordered_set y similares, y usarlas para acceder o modificar elementos existentes de manera eficiente. Lo siguiente es solo una maqueta, no el contenedor exacto que estoy implementando. El problema está en definir una función hash para el tipo de referencia.

template <class T> 
class Container { 
public: 
    class Reference { 
    public: 
     // operator==, operator!=, operator< ...., isNull() 
    private: 
     size_t index; // index into m_entries (or could be anything else) 
     // possibly more stuff 
    }; 

    Reference insert (const T &value); 
    Reference find (const T &value); 
    void remove (Reference r); 
    Reference first(); 
    Reference next (Reference prev); 

private: 
    struct Entry { T value, ... }; 

    std::vector<Entry> m_entries; 
}; 
+0

¿Cuál es el caso de uso para esto? ¿Tal vez será más fácil simplemente especificar una función hash explícita para su contenedor? –

Respuesta

2

La respuesta a esta pregunta es que lo que estás tratando de hacer es simplemente imposible de hacer. El compilador no puede determinar qué clase externa contiene un subtipo. Considere por qué:

struct outer1 { typedef int Sub; }; 
struct outer2 { typedef int Sub; }; 

¿Cómo se supone que el compilador debe determinar qué elemento externo desea cuando obtiene un Sub? No puede. No hay forma posible de que esto funcione.

En su caso, podría ser remotamente posible, aunque extremadamente difícil, derivar IFF Sub dependiente de T. Pero esto requeriría que el compilador supiera de dónde viene el Sub y simplemente no lo hace.

Así que simplemente no puede hacer esto. De ninguna manera, no cómo.

Si necesita un enfoque genérico para encontrar una función hash para sus tipos, tendrá que hacer una metafunción get_hash. Podría buscar un typedef interno "hash_type" de forma predeterminada y anularse para los hashes estándar. Lote de mecanografía ...

Alternativamente, coloque el Sub de la clase que lo contiene como su propia plantilla y tenga un typedef allí en lugar de inner class. Entonces puedes especializar hash en tu plantilla.

De lo contrario, solo proporcione el hash que conoce que necesita para el parámetro de la función hash de la plantilla que está utilizando.

+0

Sí, tiene razón; en este ejemplo, cuando el compilador obtiene un 'int', no puede saber qué hash usar. Sin embargo, en mi ejemplo, no uso un typedef, sino una clase anidada, que no es un alias para nada más; cuando el compilador obtiene un Sub, ya (creo) sabe a qué clase principal pertenece. –

+0

@AmbrozBizjak - No funciona y no puede. Necesitas sacar tu clase interna y usar un typedef en su lugar. Si no me crees, vas a perder mucho tiempo golpeándote la cabeza contra la pared ... en serio. C++ no respaldará lo que está tratando de hacer, no de la manera en que lo está haciendo. Creo que encontrarás aproximadamente mil preguntas similares aquí en stackoverflow y la respuesta es siempre la misma. –

+0

Está bien, ya veo, gracias. Moví la clase afuera y funciona bien ahora. –

0

No se puede especializar una plantilla en un tipo dependiente como ese. Intentaré algo como esto.

struct yes {}; 

template <typename T> 
yes accept_baz_sub(typename Baz<T>::Sub&&); 
void accept_baz_sub(...); 

template <class T> 
struct is_baz_sub : std::is_same<decltype(accept_baz_sub(std::declval<T>())), yes> 
{}; 

template <typename BazSub> 
using CheckedBazSub = typename std::enable_if<is_baz_sub<BazSub>::value, BazSub>::type; 

namespace std { 
    template <class BazSub> 
    struct hash<CheckedBazSub<BazSub>>; 
} 

Editar: Esta no acaba de funcionar, de acuerdo con [temp.alias] §14.5.7.2 un nombre de plantilla de alias no se deduce.

Otra solución sería escribir su propio objeto de función hash y dejar que el contenedor use eso (tercer parámetro de plantilla de std::unordered_map, por ejemplo).

+0

"error: los argumentos de plantilla predeterminados no se pueden usar en especializaciones parciales" –

4

Simplemente extraiga la clase de referencia de Container.

template <class Container> 
class Reference { 
public: 
    typedef typename Container::value_type value_type; // etc... 

    // operator==, operator!=, operator< ...., isNull() 
private: 
    size_t index; // index into m_entries (or could be anything else) 
    // possibly more stuff 
}; 

template <class T> 
class Container { 
public: 
    typedef ::Reference<Container> Reference; 
    friend class Reference; // If you cannot help it 

    typedef T value_type; 

    Reference insert (const T &value); 
    Reference find (const T &value); 
    void remove (Reference r); 
    Reference first(); 
    Reference next (Reference prev); 

private: 
    struct Entry { T value, ... }; 

    std::vector<Entry> m_entries; 
}; 

se especializan así:

namespace std { 
    template <typename Container> 
    struct hash<Reference<Container>>; 
} 
+0

Gracias, eso es lo que hice; sin embargo, acepté la respuesta de Eddie porque la sugirió primero. –

Cuestiones relacionadas