Ich habe einige Beispiele für C ++ gesehen, in denen Vorlagenvorlagenparameter (dh Vorlagen, die Vorlagen als Parameter verwenden) für das richtlinienbasierte Klassendesign verwendet werden. Welche anderen Verwendungszwecke hat diese Technik?
c++
templates
template-templates
Ferruccio
quelle
quelle
Antworten:
Ich denke, Sie müssen die Vorlagenvorlagensyntax verwenden, um einen Parameter zu übergeben, dessen Typ eine Vorlage ist, die von einer anderen Vorlage wie dieser abhängig ist:
Hier
H
ist eine Vorlage, aber ich wollte, dass diese Funktion alle Spezialisierungen von behandeltH
.HINWEIS : Ich programmiere seit vielen Jahren C ++ und habe dies nur einmal benötigt. Ich finde, dass es eine selten benötigte Funktion ist (natürlich praktisch, wenn Sie es brauchen!).
Ich habe versucht, mir gute Beispiele auszudenken, und um ehrlich zu sein, ist dies die meiste Zeit nicht notwendig, aber lassen Sie uns ein Beispiel erfinden. Stellen wir uns vor,
std::vector
das hätte keinetypedef value_type
.Wie würden Sie also eine Funktion schreiben, die Variablen des richtigen Typs für die Vektorelemente erstellen kann? Das würde funktionieren.
HINWEIS : Hat
std::vector
zwei Vorlagenparameter, Typ und Allokator, daher mussten wir beide akzeptieren. Glücklicherweise müssen wir aufgrund des Typabzugs den genauen Typ nicht explizit ausschreiben.die Sie so verwenden können:
oder noch besser, wir können einfach verwenden:
UPDATE : Auch dieses erfundene Beispiel ist zwar illustrativ, aber aufgrund der Einführung von C ++ 11 kein erstaunliches Beispiel mehr
auto
. Jetzt kann dieselbe Funktion geschrieben werden als:So würde ich es vorziehen, diese Art von Code zu schreiben.
quelle
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
und nichtf<vector<int>>
.f<vector,int>
bedeutetf<ATemplate,AType>
,f<vector<int>>
bedeutetf<AType>
Tatsächlich ist die Verwendung von Vorlagenvorlagenparametern ziemlich offensichtlich. Sobald Sie erfahren, dass C ++ stdlib eine Lücke aufweist, in der keine Stream-Ausgabeoperatoren für Standardcontainertypen definiert werden, schreiben Sie Folgendes:
Dann würden Sie herausfinden, dass der Code für den Vektor genau der gleiche ist, denn forward_list ist der gleiche, selbst für eine Vielzahl von Kartentypen ist er immer noch der gleiche. Diese Vorlagenklassen haben außer der Meta-Schnittstelle / dem Meta-Protokoll nichts gemeinsam. Durch die Verwendung des Vorlagenvorlagenparameters kann die Gemeinsamkeit in allen Klassen erfasst werden. Bevor Sie jedoch mit dem Schreiben einer Vorlage fortfahren, sollten Sie einen Verweis überprüfen, um sich daran zu erinnern, dass Sequenzcontainer zwei Vorlagenargumente akzeptieren - für Werttyp und Allokator. Während der Allokator standardmäßig aktiviert ist, sollten wir seine Existenz dennoch in unserem Vorlagenoperator << berücksichtigen:
Voila, das funktioniert automatisch für alle gegenwärtigen und zukünftigen Sequenzcontainer, die dem Standardprotokoll entsprechen. Um dem Mix Karten hinzuzufügen, muss ein Blick auf die Referenz geworfen werden, um festzustellen, dass sie 4 Vorlagenparameter akzeptieren. Daher benötigen wir eine andere Version des Operators << oben mit 4-arg-Vorlagenvorlagenparametern. Wir würden auch sehen, dass std: pair versucht, mit dem 2-arg-Operator << für zuvor definierte Sequenztypen gerendert zu werden, sodass wir eine Spezialisierung nur für std :: pair bereitstellen würden.
Übrigens, mit C + 11, das verschiedene Vorlagen zulässt (und daher Argumente für Vorlagenvorlagen zulassen sollte), wäre es möglich, einen einzigen Operator << zu haben, um sie alle zu regieren. Beispielsweise:
Ausgabe
quelle
__PRETTY_FUNCTION__
, das unter anderem Vorlagenparameterbeschreibungen im Klartext meldet. Clang macht es auch. Manchmal eine sehr praktische Funktion (wie Sie sehen können).Hier ist ein einfaches Beispiel aus 'Modern C ++ Design - Generische Programmierung und angewandte Entwurfsmuster' von Andrei Alexandrescu:
Er verwendet eine Klasse mit Vorlagenvorlagenparametern, um das Richtlinienmuster zu implementieren:
Er erklärt: Normalerweise kennt die Hostklasse das Vorlagenargument der Richtlinienklasse bereits oder kann es leicht ableiten. Im obigen Beispiel verwaltet WidgetManager immer Objekte vom Typ Widget. Daher ist es redundant und potenziell gefährlich, wenn der Benutzer Widget bei der Instanziierung von CreationPolicy erneut angeben muss. In diesem Fall kann der Bibliothekscode Vorlagenvorlagenparameter zum Festlegen von Richtlinien verwenden.
Der Effekt ist, dass der Client-Code 'WidgetManager' auf elegantere Weise verwenden kann:
Anstelle der umständlicheren und fehleranfälligeren Methode, für die eine Definition ohne Vorlagenvorlagenargumente erforderlich gewesen wäre:
quelle
Hier ist ein weiteres praktisches Beispiel aus meiner CUDA Convolutional Neural Network Library . Ich habe die folgende Klassenvorlage:
Dies implementiert tatsächlich die Manipulation von n-dimensionalen Matrizen. Es gibt auch eine untergeordnete Klassenvorlage:
Dies implementiert die gleiche Funktionalität, jedoch in der GPU. Beide Vorlagen können mit allen Grundtypen wie float, double, int usw. verwendet werden. Außerdem habe ich eine Klassenvorlage (vereinfacht):
Der Grund für die Syntax der Vorlagenvorlage liegt darin, dass ich die Implementierung der Klasse deklarieren kann
Dies hat sowohl Gewichte als auch Eingaben vom Typ float und auf der GPU, aber connection_matrix ist immer int, entweder auf der CPU (durch Angabe von TT = Tensor) oder auf der GPU (durch Angabe von TT = TensorGPU).
quelle
Angenommen, Sie verwenden CRTP, um eine "Schnittstelle" für eine Reihe von untergeordneten Vorlagen bereitzustellen. und sowohl das Elternteil als auch das Kind sind in anderen Vorlagenargumenten parametrisch:
Beachten Sie die Duplizierung von 'int', bei der es sich tatsächlich um denselben Typparameter handelt, der für beide Vorlagen angegeben wurde. Sie können eine Vorlagenvorlage für DERIVED verwenden, um diese Duplizierung zu vermeiden:
Beachten Sie, dass Sie die direkte Bereitstellung der anderen Vorlagenparameter für die abgeleitete Vorlage eliminieren . Die "Schnittstelle" empfängt sie weiterhin.
Auf diese Weise können Sie auch Typedefs in der "Schnittstelle" erstellen, die von den Typparametern abhängen, auf die über die abgeleitete Vorlage zugegriffen werden kann.
Das obige typedef funktioniert nicht, da Sie nicht in eine nicht angegebene Vorlage typedef können. Dies funktioniert jedoch (und C ++ 11 unterstützt native Vorlagen-Typedefs):
Sie benötigen leider einen derivative_interface_type für jede Instanziierung der abgeleiteten Vorlage, es sei denn, es gibt einen anderen Trick, den ich noch nicht gelernt habe.
quelle
derived
ohne ihre Vorlagenargumente verwendet werden kann, dh die Zeiletypedef typename interface<derived, VALUE> type;
template <typename>
. In gewissem Sinne können Sie sich die Vorlagenparameter als einen "Metatyp" vorstellen. Der normale Metatyp für einen Vorlagenparameter ist,typename
was bedeutet, dass er von einem regulären Typ gefüllt werden muss. Dertemplate
Metatyp bedeutet, dass er mit einem Verweis auf eine Vorlage gefüllt werden muss. Definiertderived
eine Vorlage, die einentypename
metatypisierten Parameter akzeptiert , sodass sie zur Rechnung passt und hier referenziert werden kann. Sinn ergeben?typedef
. Sie können das Duplikatint
in Ihrem ersten Beispiel auch vermeiden, indem Sie ein Standardkonstrukt wie avalue_type
vom Typ DERIVED verwenden.typedef
Problem ab Block 2 umgehen können. Aber Punkt 2 ist gültig, denke ich ... ja, das wäre wahrscheinlich eine einfachere Möglichkeit, dasselbe zu tun.Dies ist, was ich getroffen habe:
Kann gelöst werden um:
oder (Arbeitscode):
quelle
Bei der von pfalcon bereitgestellten Lösung mit variadischen Vorlagen fiel es mir aufgrund der gierigen Natur der variadischen Spezialisierung schwer, den ostream-Operator für std :: map tatsächlich zu spezialisieren. Hier ist eine kleine Überarbeitung, die für mich funktioniert hat:
quelle
Hier ist eine Verallgemeinerung von etwas, das ich gerade benutzt habe. Ich poste es, da es ein sehr einfaches Beispiel ist und einen praktischen Anwendungsfall zusammen mit Standardargumenten zeigt:
quelle
Es verbessert die Lesbarkeit Ihres Codes, bietet zusätzliche Typensicherheit und spart einige Compiler-Anstrengungen.
Angenommen, Sie möchten jedes Element eines Containers drucken, können Sie den folgenden Code ohne Vorlagenvorlagenparameter verwenden
oder mit Vorlagenvorlagenparameter
Angenommen, Sie übergeben eine Ganzzahl
print_container(3)
. Im ersten Fall wird die Vorlage vom Compiler instanziiert, der sich über die Verwendungc
in der for-Schleife beschwert. Im zweiten Fall wird die Vorlage überhaupt nicht instanziiert, da kein passender Typ gefunden werden kann.Wenn Ihre Vorlagenklasse / -funktion so ausgelegt ist, dass sie die Vorlagenklasse als Vorlagenparameter behandelt, ist es im Allgemeinen besser, dies klar zu machen.
quelle
Ich benutze es für versionierte Typen.
Wenn Sie einen Typ haben, der über eine Vorlage wie z. B. versioniert wurde
MyType<version>
, können Sie eine Funktion schreiben, in der Sie die Versionsnummer erfassen können:Sie können also abhängig von der Version des übergebenen Typs verschiedene Dinge tun, anstatt für jeden Typ eine Überladung zu haben. Sie können auch Konvertierungsfunktionen haben , die in nehmen
MyType<Version>
und zurückMyType<Version+1>
, in allgemeiner Weise und Rekursion sie sogar habenToNewest()
Funktion , die die neueste Version einer Art von jeder älteren Version (sehr nützlich für die Protokolle zurückgibt , die eine Weile zurück gespeichert haben könnte müssen aber mit dem neuesten Tool von heute verarbeitet werden).quelle