2015-09-26 13 views
10

Ho un tipo di enumerazione definita all'interno di una classe, e voglio creare un unordered_set di questi oggetti come membro della classe:Come sovrascrivere std :: hash per un enum definito all'interno di una classe?

#include <unordered_set> 

class Foo { 
public: 
    enum Bar { 
    SOME_VALUE 
    }; 

    // Error: implicit instantiation of std::hash 
    std::unordered_set<Bar> getValues() const { 
    return _values; 
    } 

private: 
    std::unordered_set<Bar> _values; 
}; 

Ora, so che la risposta ovvia è quella di aggiungere un funzione di hash personalizzata al unordered_set:

std::unordered_set<Bar, BarHasher> 

Tuttavia, quello che mi chiedo è se c'è un modo per specializzarsi std :: hash per l'enumerazione barra in modo che chi utilizza unordered_map ottiene automaticamente il comportamento di hashing.

Questo funziona con ogni altro tipo di dati, ma non enumerazioni - perché le enumerazioni non possono essere inoltrate dichiarate.

Per far funzionare tutto questo, dovrei mettere la definizione di std :: hash dopo la definizione enum, ma prima del primo utilizzo, il che significa che dovrei metterlo nel mezzo della classe corpo, che non funzionerà.

+0

Le enumerazioni possono essere inoltrate, ma non vedo come possa essere utile. – chris

+1

Questo problema è stato risolto in C++ 14: http://stackoverflow.com/a/29618545/951890 –

risposta

3

Tuttavia, quello che mi chiedo è se c'è un modo per specializzarsi std :: hash per l'enumerazione barra in modo che chi utilizza unordered_map ottiene automaticamente il comportamento di hashing.

Non ci sono miracoli, quindi chiunque utilizzerà lo specialista std::hash solo dopo la sua specializzazione. Poiché non è possibile specializzare le classi all'interno di un'altra classe e l'enumerazione è nidificata, sarà problematico utilizzare std::hash all'interno della classe. Come hai indicato le enumerazioni non puoi essere dichiarato in avanti. Quindi esiste l'unica soluzione (senza creare classi di base o enumerazioni "inanimate") per utilizzare la specializzazione std::hash all'interno della classe: aggregazione/dichiarazione per riferimento e utilizzo all'esterno dopo la specializzazione std::hash.

#include <iostream> 
#include <unordered_set> 
#include <memory> 

struct A { 

    enum E { 
     first, second 
    }; 

    A(); 

    std::unique_ptr< std::unordered_set<E> > s_; //!< Here is 
}; 

namespace std { 

template<> 
class hash<A::E> { 
public: 
    std::size_t operator()(A::E const& key) const noexcept { 
     std::cout << "hash<A::E>::operator()" << std::endl; 
     return key; 
    } 

}; 

} 

A::A() 
    : s_(new std::unordered_set<E>) 
{ } 

int main(void) { 
    A a; 
    a.s_->insert(A::first); 

    std::unordered_set<A::E> s; 
    s.insert(A::second); 
} 

Stampe fuori

hash < A :: E> :: operator()
hash < A :: E> :: operator()

Quindi, al di fuori la classe A tutti possono usare A::E con un std::hash e nella classe interna si usa anche A::E con std::hash. Inoltre, se non si desidera aggregare std::unordered_set per riferimento, è possibile implementare hasher personalizzati solo per uso interno (quindi inoltrare le chiamate a std::hash).

1

Sembra che tu abbia già coperto tutti gli angoli della tua domanda.

Non riesco a pensare a un modo per farlo.

per ricapitolare, è possibile modificare solo i fatti della situazione:

  • rendono il enum non annidata (metterlo in uno spazio dei nomi che racchiude invece), o
  • utilizzare la funzione hasher esplicitamente come in il tuo esempio.
3

Una possibilità è mettere l'enum in una classe base. Sfortunatamente, devi fornire una dichiarazione usando per ogni membro di enum. Un modo per aggirare è quello di utilizzare un enumerato ambito (enum class Bar), che richiede l'uso come Foo::Bar::SOME_VALUE anziché Foo::SOME_VALUE. In questo modo, avresti solo bisogno dello using FooBase::Bar;.

class FooBase { 
public: 
    enum Bar { 
    SOME_VALUE 
    }; 

protected: 
    ~FooBase() = default; //so can't be used polymorphically 
}; 

//hash goes here 

class Foo : FooBase { 
public: 
    using FooBase::Bar; 
    using FooBase::SOME_VALUE; 
    ... 
+0

Ooh è inventivo. Non lo userei, ma ho ridacchiato. :) –