Wie kann man mit type_traits Code generieren, der davon abhängt, ob eine Klassenspezialisierung existiert?

7

HINTERGRUND

Ich versuche ein class template Hasher zu schreiben, das auf zwei verschiedene Arten implementiert wird, abhängig davon, ob std::hash<T> für T implementiert wurde:

%Vor%

Wenn std::hash<T> spezialisiert wurde, möchte ich es verwenden. Wenn nicht, erwarte ich, dass T eine to_string() -Funktion hat, um einen Schlüssel zurückzugeben, auf dem ich hacken kann.

Wenn z. B. nach cppreference T ist long long , ein Zeiger, oder std::string , ich möchte Version A. Wenn es nicht eine der aufgeführten Standardversionen ist und der Benutzer sich nicht auf std::hash<T> für seinen eigenen Typ spezialisiert hat, erwarte ich, dass T eine std::string to_string() const für mich hat . In diesem Fall möchte ich Version B generieren.

PROBLEM

Wie verwende ich C ++ 11 / type_traits / no-SFINAE, um die richtige Implementierung zu generieren?

ANHANG

Eine andere Möglichkeit, darüber nachzudenken:

Es ist fast so, als ob Version B das Standardverhalten sein soll (dh wenn keine Spezialisierung existiert, Version B).

GETESTETE NAWAZ-LÖSUNG

Ich habe gerade Nawaz 'Lösung auf gcc 4.8.1 ausprobiert, da er zuerst kam und für mich am einfachsten zu lesen und zu verstehen ist (wichtiger).

%Vor%

GETESTETE DANIEL FREY'S LÖSUNG

Daniels Implementierung ist auch sehr interessant. Indem ich zuerst rechne, ob wir einen Hash haben oder nicht, kann ich mit dem Tag-Versand die gewünschte Implementierung auswählen. Wir haben ein schönes Muster / Trennung von Bedenken, was zu sehr sauberem Code führt.

Allerdings haben mich die Argumente von has_hash<> in der Implementierung von decltype zunächst verwirrt. In der Tat sollte es nicht als Argumente lesen. Es ist vielmehr eine Liste von Ausdrücken (durch Komma getrennte Ausdrücke). Wir müssen die Regeln befolgen, wie sie hier ausgedrückt sind.

  

C ++ stellt sicher, dass jeder der Ausdrücke ausgewertet wird und seine Seite   Effekte finden statt. Allerdings ist der Wert eines ganzen Kommas getrennt   Ausdruck ist nur das Ergebnis des Ausdrucks ganz rechts.

Auch die Verwendung von void() war mir zunächst ein Rätsel. Als ich es in double() geändert habe, um zu sehen, was passieren würde, war klar, warum es wirklich void() sein sollte (also müssen wir diesen zweiten Template-Parameter nicht übergeben).

%Vor%     
kfmfe04 26.12.2013, 17:08
quelle

4 Antworten

10

Sie könnten Expression SFINAE verwenden, das von C ++ 11 eingeführt wurde.

Hier ist ein Beispiel, wie es implementiert wird:

%Vor%

Beachten Sie, dass hash_impl eine überladene Funktionsvorlage ist. Also wenn du das schreibst:

%Vor%

Da das zweite Argument 0 int ist, versucht der obige erste hash_impl aufzurufen, der std::hash verwendet - dieser Versuch schlägt möglicherweise fehl, wenn std::hash<U>().hash(u) ist kein gültiger Ausdruck ( Ausdruck SFINAE ). Wenn das fehlschlägt, wird das zweite hash_impl aufgerufen.

    
Nawaz 26.12.2013, 17:15
quelle
5

Sie könnten einfach testen, ob std::hash<T>()(...) mit dem fraglichen Typ aufgerufen werden kann. Wenn dies der Fall ist, erhalten Sie einen Typ von decltype() , der in einem SFINAE-Ausdruck verwendet werden kann, um die Größe eines Rückgabetyps zu bestimmen:

%Vor%

Darauf basierend könnten Sie dann mit has_hash<T>::value feststellen, ob bereits eine verwendbare Hash-Funktion definiert ist.

    
Dietmar Kühl 26.12.2013 17:27
quelle
5

Wenn Sie testen müssen, ob ein Ausdruck gültig ist oder nicht, bevorzuge ich die folgende Implementierung:

%Vor%

Live-Beispiel

    
Daniel Frey 26.12.2013 19:19
quelle
2

Noch eine Implementierung

Erstens, einige Standard-Tabellen. type_sink und TypeSink lassen Sie uns Typen auswerten und verwerfen.

%Vor%

Wir schreiben dann eine wirklich einfache has_hash , die SFINAE verwendet. Die Standardauswahl ist "no hash" und die Spezialisierung ist gültig, wenn std::hash<T>()( t ) ein gültiger Ausdruck für eine Variable t vom Typ T :

ist %Vor%

Dann schreiben wir unseren universellen Hasher:

%Vor%

Hier verwenden wir das Merkmal has_hash , um die getaggte Ausgabe entweder an die "usage hash direkt" oder " to_string then hash " vorzunehmen.

Wir können mehr Schichten dieser Dispatching machen.

Ich habe mir erlaubt, es etwas bewegungsbewusst zu machen, indem T T& oder T const& oder T sein kann und sich vernünftig verhält. (Ich weiß nicht über T&& von der Spitze meines Kopfes).

Wie in anderen Kommentaren erwähnt, können einige Arbeiten zur Erzeugung besserer Fehlermeldungen durchgeführt werden. Wir möchten, dass die Fehlermeldung des Compilers sich über das Fehlen einer hash<T> Implementierung anstatt einer to_string Implementierung beschweren kann. Das würde beinhalten, eine Klasse has_to_string traits zu schreiben und entweder eine weitere Ebene der Tag-Verteilung auszuführen oder eine static_assert auszuführen, um eine nützliche Nachricht zu erzeugen, die den Endbenutzer anweist, entweder hash<T> specialization oder T::to_string zu implementieren.

Eine andere Möglichkeit wäre, einen universellen Hasher zu erstellen:

%Vor%

was die Umwandlung in T bis zum letzten Moment vorschreibt.

    
Yakk 26.12.2013 19:21
quelle

Tags und Links