Einführung in die Template-Spezialisierung

Modernes C++ Rainer Grimm  –  68 Kommentare

Templates definieren das Verhalten von Familien von Klassen oder Funktionen. Oft ist es erforderlich, spezielle Typen oder Nicht-Typen besonders zu behandeln. Um diesen Anwendungsfall zu erfüllen, kannst du Templates spezialisieren.

Template-Spezialisierung

Zunächst möchte ich die allgemeine Idee hinter der Template-Spezialisierung beleuchten. Im nächsten Artikel konzentriere ich mich auf die Details.

Template-Spezialisierung

Templates definieren das Verhalten von Familien von Klassen und Funktionen. Oft ist es erforderlich, spezielle Typen oder Nicht-Typen besonders zu behandeln. Dazu kannst du Templates vollständig spezialisieren.

Klassen-Templates lassen sich auch teilweise spezialisieren. Das allgemeine oder primäre Template kann mit teilweise oder vollständig spezialisierten Templates koexistieren. Die Memberfunktionen und Attribute einer Spezialisierung müssen nicht identisch mit denen des primären Templates sein. Der Compiler bevorzugt vollständig spezialisierte gegenüber teilweise spezialisierten Templates, und teilweise spezialisierte Templates gegenüber primären Templates.

Das folgende Beispiel soll meine Worte verdeutlichen:

template <typename T, int Line, int Column> // (1)
class Matrix;

template <typename T> // (2)
class Matrix<T, 3, 3>{};

template <> // (3)
class Matrix<int, 3, 3>{};
  • Primäre Templates

Zeile 1 beschreibt das primäre oder allgemeine Template. Das primäre Template muss vor den teilweise oder vollständig spezialisierten Templates deklariert werden. Wird das primäre Template nicht benötigt, ist eine Deklaration wie in Zeile 1 in Ordnung.

  • Partielle Spezialisierung

In Zeile 2 folgt die partielle Spezialisierung. Nur Klassen-Templates unterstützen partielle Spezialisierung. Eine partielle Spezialisierung hat Template-Parameter und explizit angegebene Template-Argumente. Im konkreten Fall der Klasse Matrix<T, 3, 3> ist T der Template-Parameter und die Zahlen sind die Template-Argumente.

  • Vollständige Spezialisierung

Zeile 3 zeigt eine vollständige Spezialisierung. Vollständig bedeutet, dass alle Template-Argumente angegeben sind und die Template-Parameterliste leer ist: template <> in Zeile 3.

Partielle versus vollständige Spezialisierung

Um die partielle und vollständige Spezialisierung besser zu verstehen, möchte ich eine visuelle Erklärung präsentieren. Du weißt vielleicht, dass ich Mathematik studiert habe und viele lineare Gleichungssysteme zu lösen hatte.

Denke an einen n-dimensionalen Raum von Template-Parametern. Eine partielle Spezialisierung ist ein Unterraum im n-dimensionalen Raum, und eine vollständige Spezialisierung ist ein Punkt im n-dimensionalen Raum.

Nun wende ich meine visuelle Erklärung auf das Klassen-Template Matrix und ihre partielle und vollständige Spezialisierung an. In dem primären Template (Zeile 1) kannst du einen Typ als Template-Parameter und zwei int-Werte als Nicht-Typ-Template-Parameter wählen. Bei der partiellen Spezialisierung in Zeile 2 lässt sich nur der Typ auswählen. Das bedeutet, dass der 3-dimensionale Raum auf eine Linie reduziert wird. Die partielle Spezialisierung des primären Templates Matrix ist somit ein Unterraum des 3-dimensionalen Raumes. Die volle Spezialisierung (Zeile 3) steht für einen Punkt im 3-dimensionalen Raum.

Was passiert, wenn du das Template verwendest?

Verwendung der primären, partiellen und vollständigen Spezialisierung

Zur Erinnerung: Die folgenden Spezialisierungen der Klasse Matrix sind gegeben:

template <typename T, int Line, int Column> // (1)
class Matrix;

template <typename T> // (2)
class Matrix<T, 3, 3>{};

template <> // (3)
class Matrix<int, 3, 3>{};

Die Frage ist: Was passiert, wenn du Matrix für verschiedene Template-Argumente instanziierst? Das folgende Beispiel zeigt drei Instanziierungen und deren Umsetzung durch den Compiler:

Matrix<int, 3, 3> m1; // class Matrix<int, 3, 3>

Matrix<double, 3, 3> m2; // class Matrix<T, 3, 3>

Matrix<std::string, 4, 3> m3; // class Matrix<T, Line, Column> => ERROR

m1 verwendet die vollständige Spezialisierung, m2 die partielle Spezialisierung und m3 das primäre Template. Da die Definition fehlt, verursacht die Verwendung von m3 einen Fehler.

Um diesen Prozess zu verstehen, musst du ein paar Regeln im Hinterkopf behalten. Die folgenden Regeln gelten insbesondere für die partielle Spezialisierung von Klassen-Templates:

  • Abhängigkeiten zwischen dem Template-Parameter und den Template-Argumenten
    • Die Anzahl und Reihenfolge der explizit angegebenen Template-Argumente (<T, 3, 3>) muss mit der Anzahl und Reihenfolge der Template-Parameterliste (<typename T, int Line, int Column>) des primären Templates übereinstimmen.
    • Wenn du Standardwerte für Template-Parameter verwendest, brauchst du die Template-Argumente nicht anzugeben. Nur das primäre Template akzeptiert Defaultwerte für Template-Parameter.
  • Gültige partielle Spezialisierungen
    • Der Compiler wählt eine partielle Spezialisierung, wenn die Argumente der Template-Instanziierung (Matrix<double, 3, 3>) eine Teilmenge der Template-Argumente der partiellen Spezialisierung (Matrix<T, 3, 3>) sind.
  • Verwendete Template-Spezialisierung
    • Der Compiler findet nur eine Spezialisierung. Er verwendet diese Spezialisierung.
    • Der Compiler findet mehr als eine Spezialisierung. Er verwendet die am meisten spezialisierte. Wenn dieser Prozess in mehr als einer Spezialisierung endet, wirft der Compiler einen Fehler.
    • Der Compiler findet keine Spezialisierung. Er verwendet die primäre Spezialisierung.

Okay, eine Frage muss ich noch beantworten. Was bedeutet es, dass ein Template A ein spezialisierteres Template ist als ein spezialisiertes Template B. Meine informelle Definition lautet wie folgt:

Ein Template A ist spezialisierter als ein Template B:

  • Das Template B kann alle Argumente akzeptieren, die Template A akzeptieren kann.
  • Das Template B kann Argumente akzeptieren, die Template A nicht akzeptieren kann.

Wenn du es formaler haben willst, besuche cppreference.com/partial_specialization und lies den Unterabschnitt über partial ordering.

Wie geht's weiter?

Dieser Artikel sollte dir die Grundlagen zur Template-Spezialisierung vermitteln, aber wie immer gibt es in C++ mehr Details dazu. Zum Beispiel verhält sich partielle oder vollständige Spezialisierung wie ein if zur Compilezeit und vollständige Spezialisierung von Klassen- oder Funktions-Templates sind den normalen Klassen oder Funktionen sehr ähnlich.

C++ Seminare

Im nächsten halben Jahr biete ich die folgenden Seminare an. Falls es die Covid-19 Situation zulässt, werde ich die Seminare nach Rücksprache als Präsenzseminare durchführen.