Nehmen wir an, wir hätten eine template
class Foo
:
Ich habe eine andere template
class Bar
(unabhängig von der ersten):
Sagen wir, ich möchte die Methode foo()
für jede Klasse Bar
spezialisieren.
Ich würde fälschlicherweise schreiben:
Der Compiler gibt mir die Schuld, weil der Typ nicht vollständig ist:
%Vor%Ich verwende C ++ 98 , aber ich würde gerne wissen, ob es in C ++ 11 verschiedene Lösungen gibt.
Ich könnte das Problem lösen, indem ich die gesamte Klasse Foo
für eine generische Bar
spezialisiere, aber nachdem ich alle Methoden definieren müsste.
Das ist nicht, was ich will, ich suche nach (wenn existiert) elegantere Lösung (sowohl C ++ 98 als auch C ++ 11), die mir erlaubt, mich zu spezialisieren und nur eine einzige Klassenmethode zu implementieren.
Die Frage zu SO erklärt nicht, wie man sich mit einem Template-Argument spezialisieren kann. Tatsächlich zeigt meine Frage, wie sich der Compiler darüber beschwert.
Für C ++ 11 kann SFINAE zwei verschiedene Versionen von std::enable_if
innerhalb einer nicht spezialisierten foo()
-Klasse aktivieren / deaktivieren (mit Foo
).
In C ++ 98 hast du nicht Sorry: meine Idee funktioniert nicht weil diese Methode die Verwendung von Standardschablonenargumenten für Methoden erfordert, die eine C ++ 11-Innovation sind. std::enable_if
, aber du kannst es simulieren (gib mir ein paar Minuten und ich versuche ein Beispiel vorzuschlagen).
Eine andere Möglichkeit besteht darin, eine Template-Basisklasse für Foo()
zu definieren, zB FooBase
, foo()
(und nur foo()
) in FooBase
und specialize FooBase
.
Ein anderer Weg, der auch mit C ++ 98 funktioniert, kann das Tag-Dispatching sein: Sie können ein eindeutiges foo()
mit Null-Parameter definieren, das ein anderes foo()
aufruft, mit einem Parameter, der durch T
bestimmt wird .
Das Folgende ist ein vollständiges (C ++ 98 kompilierbares) Beispiel
%Vor%Wenn eine gemeinsame Basis nicht wünschenswert ist, kann ein anderer Weg foo () einen Anpassungspunkt geben, wie zum Beispiel ein Merkmal:
%Vor%Eine solche Eigenschaft könnte auch ein Implementierungsdetail-Freund sein, wenn ihr einziger Zweck darin besteht, intern eine foo () Spezialisierung für Bars bereitzustellen.
Wenn Sie foo
nicht spezialisieren können, definieren Sie es so, dass es den Aufruf an eine interne foo-implementation -Klasse delegiert. Dann spezialisiere diese Klasse.
So etwas sollte in C ++ 98 kompiliert werden und es unterscheidet sich nicht viel von Ihrem ursprünglichen Code:
Die letzte Zeile kompiliert nicht wie erwartet (zumindest habe ich das Ergebnis erwartet, einfach den Funktionsaufrufoperator für FooImpl
anders definieren).
Auf diese Weise können Sie selektiv die Spezialisierungen definieren, für die foo
funktionieren soll. In allen anderen Fällen führt ein Versuch, foo
zu verwenden, zu einem Kompilierungsfehler.
Ich würde gerne wissen, ob es in C ++ 11 verschiedene Lösungen gibt.
Dies ist ein klassischer Anwendungsfall für getaggte Versendungen, von denen max66 bereits vorgeschlagen wurde. Der Ansatz und sogar die Syntax sind im Wesentlichen in C ++ 98 und C ++ 11 identisch.
Hier ist eine etwas sauberere Implementierung als die von max66, glaube ich ( läuft auf godbolt ):
%Vor% Das Prinzip ist das gleiche; Eine Clientfunktion, die keine Argumente akzeptiert, ruft eine Hilfsfunktion auf, die basierend auf dem T
-Argument einen leeren Typ erstellt. Dann sorgt normale Überladung für den Rest.
Nur hier verwende ich eine Template-Catch-All-Methode.
In C ++ 11 würde sich die Syntax nur geringfügig ändern; Wir könnten tag<Bar<3>>
anstelle von tag<Bar<3> >
sagen, weil neue Parsing-Regeln den Chevron für verschachtelte Vorlagen erlauben.
Wir könnten auch das Tag und die Vorlage " foo_helper
catch-all" in variadischen Vorlagen einfügen etwas generischer sein:
Die Dinge beginnen tatsächlich ziemlich interessant zu werden in C ++ 17 mit der Einführung von constexpr if , das es uns ermöglicht, etwas zu schreiben, das wie normale Verzweigungslogik basierend auf T
aussieht ( Live-Demo ):
Wie Sie sehen können, geht der gesamte Tag-Kram zugunsten einer einfachen if-Anweisung verloren.
Wir nutzen type_traits , die in C ++ 11 eingeführt wurden, um den Typ von T
zu überprüfen. gegen unseren Wunschtyp. So etwas würde vorher nicht unbedingt funktionieren, da alle Zweige kompiliert werden müssen. In C ++ 17 wird nur der zum Zeitpunkt der Kompilierung ausgewählte Zweig kompiliert.
Beachten Sie, dass Sie dieses Verhalten bereits in C ++ 98 emulieren können, indem Sie typeid
verwenden ( godbolt-Demo ):
Der Ansatz typeid
ist jedoch aus zwei Gründen eine schlechte Wahl:
if constexpr
nur den ausgewählten Zweig compiliert. Tags und Links c++ c++11 templates template-specialization c++98