6

Gibt es überhaupt eine Spezialisierung für eine Vorlage wie diese, so dass die Spezialisierung nur dann gilt, wenn T eine Elementfunktion hash hat? (Beachten Sie, dass dies nur ein Beispiel für das ist, was ich versuche. Ich weiß, dass es für jede Klasse, die eine hash-Funktion hat, sinnvoller wäre, sie selbst in der Member-Funktion operator== zu überprüfen, aber ich möchte nur wissen, ob diese Art von Sache ist möglich.)Gibt es sowieso eine Vorlage auf der Grundlage der Mitglieder eines Parameters in C++ zu spezialisieren?

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

Ich würde eine pre-C++ 11-Lösung bevorzugen, wenn möglich.

+1

Sie könnten Konzepte mit dem experimentellen Compiler-Konzept GCC verwenden, aber das wird nicht Standard C++ vor ein paar Jahren sein. – Morwenn

Antwort

8

Hier ist ein Beispiel aus meinem eigenen Code. Wie Sie vielleicht von einem der Strukturnamen vermuten, basiert dies auf dem Prinzip Substitution Failure is Not an Error. Die Struktur has_member_setOrigin definiert zwei Versionen von test. Der erste kann nicht erfüllt werden, wenn U kein Mitglied setOrigin hat. Da das ist kein Fehler in einer Vorlage Substitution es verhält sich so, als ob es nicht existiert. Die Auflösungsreihenfolge für polymorphe Funktionen findet somit test(...), die ansonsten eine niedrigere Priorität hätte. Die value wird dann durch den Rückgabetyp test bestimmt.

Danach folgen zwei Definitionen von callSetOrigin (entspricht equals) mit der Vorlage enable_if. Wenn Sie enable_if untersuchen, sehen Sie, dass wenn das erste Template-Argument wahr ist, enable_if<...>::type definiert ist, andernfalls nicht. Dies erzeugt erneut einen Substitutionsfehler in einer der Definitionen von callSetOrigin, so dass nur einer überlebt.

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

vergessen I als auch eine Definition von enable_if bereitgestellt:

#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

Das ist ein großer Workaround! – Matt

+2

Dies ist eine gute Antwort, aber es wäre besser, wenn Sie eine Erklärung hinzufügen, wie es funktioniert. – zwol

2

eine C++ 11-Lösung. Ändern Sie den Rückgabetyp nur so, dass nur gültige Typen .hash haben. Außerdem verwenden wir den Kommaoperator, so dass, während der Compiler überprüft, dass es declval<T>.hash() berechnen kann, wird es das tatsächlich ignorieren und den Typ true verwenden, der natürlich bool der Typ ist, den Sie möchten.

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

Ich glaube, das heißt Expression SFINAE.

Weitere Details:

decltype(X,Y) ist die gleiche wie decltype(Y) (dank der Komma-Operator). Das bedeutet, dass mein Rückgabetyp im Wesentlichen decltype(true) ist, d.h. bool, wie gewünscht. Also warum habe ich declval<T>.hash()? Ist das nicht nur eine Verschwendung von Raum?

Die Antwort ist, dass es der Test ist, T hat eine hash Methode. Wenn dieser Test fehlschlägt, schlägt die Berechnung des Rückgabetyps fehl, und daher wird die Funktion nicht als gültige Überladung angesehen, und der Compiler sucht an anderer Stelle.

Schließlich, wenn Sie nicht declval zuvor gesehen haben, ist es eine wirklich nützliche Methode, ein Objekt vom Typ T in einem unevaluierten Kontext (wie decltype) zu erstellen.Sie könnten versucht sein, T() zu schreiben, um eine T zu konstruieren, und verwenden Sie daher T().hash(), um hash zu rufen. Aber das wird nicht funktionieren, wenn T keinen Standardkonstruktor hat. declval löst das.