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:
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).
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:
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.
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:
Darauf basierend könnten Sie dann mit has_hash<T>::value
feststellen, ob bereits eine verwendbare Hash-Funktion definiert ist.
Wenn Sie testen müssen, ob ein Ausdruck gültig ist oder nicht, bevorzuge ich die folgende Implementierung:
%Vor%Noch eine Implementierung
Erstens, einige Standard-Tabellen. type_sink
und TypeSink
lassen Sie uns Typen auswerten und verwerfen.
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
:
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.
Tags und Links c++ c++11 templates typetraits