Template-Spezialisierung mit Template-Argument

8

Nehmen wir an, wir hätten eine template class Foo :

%Vor%

Ich habe eine andere template class Bar (unabhängig von der ersten):

%Vor%

Sagen wir, ich möchte die Methode foo() für jede Klasse Bar spezialisieren. Ich würde fälschlicherweise schreiben:

%Vor%

Der Compiler gibt mir die Schuld, weil der Typ nicht vollständig ist:

%Vor%

Code

Ich verwende C ++ 98 , aber ich würde gerne wissen, ob es in C ++ 11 verschiedene Lösungen gibt.

Hinweis

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.

Beispielcode

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.

BEARBEITEN:

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.

    
Biagio Festa 07.11.2017, 15:52
quelle

4 Antworten

3

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 std::enable_if , aber du kannst es simulieren (gib mir ein paar Minuten und ich versuche ein Beispiel vorzuschlagen). Sorry: meine Idee funktioniert nicht weil diese Methode die Verwendung von Standardschablonenargumenten für Methoden erfordert, die eine C ++ 11-Innovation sind.

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%     
max66 07.11.2017, 15:59
quelle
0

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.

    
Massimiliano Janes 07.11.2017 16:20
quelle
0

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:

%Vor%

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.

    
skypjack 07.11.2017 21:05
quelle
0
  

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:

%Vor%

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 ):

%Vor%

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 ):

%Vor%

Der Ansatz typeid ist jedoch aus zwei Gründen eine schlechte Wahl:

  1. Es ist eine Laufzeitprüfung (langsam) für Informationen, die wir zur Kompilierzeit kennen
  2. Es ist spröde, weil alle Zweige für alle Template-Instanziierungen kompilieren müssen, während in C ++ 17 if constexpr nur den ausgewählten Zweig compiliert.
AndyG 08.11.2017 15:55
quelle