6

Esiste comunque una specializzazione per un modello come questo, rendendo la specializzazione applicabile solo se T ha una funzione membro hash? (Nota: questo è solo un esempio di ciò che sto cercando di fare. So che sarebbe più sensato per ogni classe che ha una funzione hash verificarla autonomamente nella funzione membro operator==, ma voglio solo sapere se questo tipo di cose è possibile.)Esiste comunque la possibilità di specializzare un modello basato sui membri di un parametro in C++?

template <class T> 
bool equals(const T &x, const T &y) 
{ 
    return x == y; 
} 

template <class T> // somehow check if T has a member function 'hash' 
bool equals<T>(const T &x, const T &y) 
{ 
    return x.hash() == y.hash() && x == y; 
} 

Preferirei una soluzione pre-C++ 11 se possibile.

+1

È possibile utilizzare concetti con il concetto sperimentale del compilatore GCC, ma questo non sarà il C++ standard prima di alcuni anni. – Morwenn

risposta

8

Ecco un esempio tratto dal mio codice. Come si può intuire da uno dei nomi della struttura, ciò si basa sul principio che Substitution Failure is Not an Error. La struttura has_member_setOrigin definisce due versioni di test. Il primo non può essere soddisfatto se U non ha un membro setOrigin. Poiché questo è non un errore in una sostituzione di modello, si comporta come se non esistesse. L'ordine di risoluzione per le funzioni polimorfiche trova quindi test(...) che altrimenti avrebbe una priorità inferiore. Lo value viene quindi determinato dal tipo restituito test.

Questo è seguito da due definizioni di callSetOrigin (equivalente a equals) utilizzando il modello enable_if. Se si esamina enable_if, si noterà che se il primo argomento del modello è true, viene definito enable_if<...>::type, altrimenti non lo è. Ciò crea nuovamente un errore di sostituzione in una delle definizioni di callSetOrigin tale che solo uno sopravvive.

template <typename V> 
struct has_member_setOrigin 
{ 
    template <typename U, void (U::*)(const Location &)> struct SFINAE {}; 
    template <typename U> static char test(SFINAE<U, &U::setOrigin>*); 
    template <typename U> static int test(...); 
    static const bool value = sizeof(test<V>(0)) == sizeof(char); 
}; 

template<typename V> 
void callSetOrigin(typename enable_if <has_member_setOrigin<V>::value, V>::type &p, const Location &loc) const 
{ 
    p.setOrigin(loc); 
} 

template<typename V> 
void callSetOrigin(typename enable_if <!has_member_setOrigin<V>::value, V>::type &p, const Location &loc) const 
{ 
} 

dimenticato ho fornito una definizione di enable_if così:

#ifndef __ENABLE_IF_ 
#define __ENABLE_IF_ 

template<bool _Cond, typename _Tp> 
struct enable_if 
{ }; 

template<typename _Tp> 
struct enable_if<true, _Tp> 
{ typedef _Tp type; }; 

#endif /* __ENABLE_IF_ */ 
+0

Questa è una soluzione alternativa! – Matt

+2

Questa è una buona risposta, ma sarebbe meglio se aggiungeste qualche spiegazione su come funziona. – zwol

2

Una soluzione C++ 11. Basta modificare il tipo di reso in modo che siano solo i tipi validi che hanno .hash. Inoltre, utilizziamo l'operatore virgola in modo che, mentre il compilatore verificherà che può calcolare declval<T>.hash(), in realtà lo ignorerà e utilizzerà il tipo di true, che è ovviamente bool, il tipo desiderato.

template <class T> 
auto equals(const T &x, const T &y) -> decltype(declval<T>.hash(), true) 
{ 
    return x.hash() == y.hash() && x == y; 
} 

Credo che questo si chiama Expression SFINAE.

Maggiori dettagli:

decltype(X,Y) è uguale decltype(Y) (grazie l'operatore virgola). Ciò significa che il mio tipo di ritorno qui è fondamentalmente decltype(true), ad esempio bool, come desiderato. Quindi, perché ho declval<T>.hash()? Non è solo uno spreco di spazio?

La risposta è che è il test che T ha un metodo hash. Se questo test fallisce, allora il calcolo del tipo restituito fallisce, e quindi la funzione non è vista come un sovraccarico valido e il compilatore cercherà altrove.

Infine, se non avete visto declval prima, è un modo molto utile per creare un oggetto di tipo T in un contesto non valutata (come decltype).Potresti essere tentato di scrivere T() per creare un T e quindi utilizzare T().hash() per chiamare hash. Ma questo non funzionerà se T non ha un costruttore predefinito. declval risolve questo.