Templates - Erste Schritte

Modernes C++ Rainer Grimm  –  3 Kommentare

Die Idee zu diesem Beitrag ist schnell erklärt. Ich möchte Templates und insbesondere den Prozess der Template-Instanziierung visualisieren. Dank C++ Insights ist diese Visualisierung einfach möglich.

Templates (Klassen-Templates oder Funktions-Templates) sind Familien von Klassen oder Funktionen. Wer ein Template instanziiert, erzeugst du eine konkrete Klasse oder eine konkrete Funktion aus dieser Familie von Klassen oder Funktionen. Hier sind die ersten einfachen Fragen, die ich beantworten möchte. Der Einfachheit halber nenne ich ein Klassen-Template häufig eine generische Klasse und ein Funktions-Template eine generische Funktion.

Wann sollte ein Template implementiert werden?

Ein Template sollte dann verwendet werden, wenn eine Funktion oder Klasse für eine generische Idee steht. Eine generische Idee ist nicht an einen konkreten Typ gebunden. Zum Beispiel lässt sich eine Funktion wie max oder ein Container wie vector für viele Datentypen verwenden.

Wie lässt sich ein Template definieren?

Als Startpunkt nehme ich eine Funktion max an, die zwei ints annimmt.

int max(int lhs, int rhs) { 
return (lhs > rhs)? lhs : rhs;
}

Eine Funktion in ein Template zu erweitern, ist im Allgemeinen einfach.

  1. Setze die Zeile template <Typname T> vor die Funktion.
  2. Ersetze den konkreten Typ int durch den Typ-Parameter T.
template <typename T> // (1) 
T max(T lhs, T rhs) { // (2)
return (lhs > rhs)? lhs : rhs;
}

Ich muss noch zwei Bemerkungen loswerden.

  1. Statt des Namens typename lässt sich auch class verwenden. Ich schlage dringend typename vor, weil T keine Klasse sein muss, sondern ein Typ, ein Nicht-Typ oder ein Template sein kann.
  2. Wir verwenden aus Konvention T als Namen für den ersten Typ-Parameter.

Das gleiche Vorgehen funktioniert auch zum Umwandeln einer Klasse in ein Klassen-Template. Jetzt komme ich genau zu dem Punkt, an dem mir C++ Insights wertvolle Dienste leistet.

Was passiert, wenn das Template instanziiert wird?

Im folgenden Beispiel instanziiere ich das Funktions-Template max für int und double.

template <typename T>
T max(T lhs, T rhs) {
return (lhs > rhs)? lhs : rhs;
}

int main() {
max(10, 5);
max(10.5, 5.5);
}

C++ Insights gibt tiefe Einsicht in den automatischen Prozess der Template Instanziierung.

Der Prozess der Template-Instanziierung erzeugt die Zeilen 6 bis 23. Lass mich ein paar Worte über die Instanziierung der Funktion max für die beiden ints schreiben (Zeilen 6 bis 13). Zeile 6 im Screenshot drückt aus, dass Zeile 8 in der Quelldatei (max(10, 5)) die Erzeugung der Zeilen 6 bis 13 verursacht. Ich nehme an, dass die ersten beiden Zeilen des vom Compiler generierten Codes die interessantesten sind.

template<> 
int max<int>(int lhs, int rhs) {
return (lhs > rhs) ? lhs : rhs;
}

max ist ein vollständig spezialisiertes Funktions-Template für int: max<int>. Der generische Teil ist leer: template<>. Der Compiler erzeugt aus der Familie der max-Funktionen eine konkrete Funktion für int. Heißt das auch, dass der Compiler für jeden verwendeten Typ eine konkrete Funktion generiert?

Was passiert, wenn ein Template mehrmals für den gleichen Typ instanziiert wird?

Mein nächstes Beispiel basiert auf einem Klassen-Template. Dieses Template ist ein einfacher Container, der mehrmals für int instanziiert wird.

template <typename T, int N>
class Array{
public:
int getSize() const{
return N;
}
private:
T elem[N];
};

int main() {

Array<int, 5> myArr1; // (1)
Array<int, 10> myArr2; // (2)
Array<int, 5> myArr3; // (3)

}

Ich habe zweimal Array<int, 5> (1) und (3) und einmal Array<int, 10> (2) instanziiert. Die Ausgabe von C++ Insights zeigt, dass die zweite Instanziierung von Array<int, 5> (3) die erste Instanziierung, die bereits durch (1) angestoßen wurde, verwendet. Hier sind die relevanten Teile der Ausgabe.

Sind wir fertig mit diesem Beispiel? Nein! Es gibt zwei weitere interessante Beobachtungen, auf die ich eingehen möchte: Erstens, der Prozess der Template-Instanziierung ist lazy. Zweitens, ich verwendete einen Nicht-Typ Template-Parameter.

Template Instanziierung ist lazy

Die Memberfunktion getSize() wurde nicht instanziiert? Nur die Deklaration der Member-Funktion ist vorhanden. Der Prozess der Template-Instanziierung ist lazy. Das heißt, wenn die Member-Funktion nicht aufgerufen wird, wird sie auch nicht instanziiert. Das geht so weit, dass ungültiger Code in einer Member-Funktion verwendet werden kann. Natürlich darf diese dann nicht aufgerufen werden. Wenn du mir nicht glaubst, dann kompiliere das folgende kleine Programm. Deaktiviere zunächst (1) und aktiviere es dann.

// number.cpp

#include <cmath>
#include <string>

template <typename T>
struct Number {
int absValue() {
return std::abs(val);
}
T val{};
};

int main() {

Number<std::string> numb;
// numb.absValue(); // (1)

}

Gehen wir zurück zu meinem vorherigen Programm und rufen getSize() auf. Hier ist das modifizierte main-Programm.

int main() {

Array<int, 5> myArr1;
Array<int, 10> myArr2;
Array<int, 5> myArr3;
myArr3.getSize(); // (1)

}

Dementsprechend zeigt der folgende Screenshot den vom Compiler generierten Code für die Memberfunktion getSize() (Zeilen 18 - 21).

int ist ein Nicht-Typ Template-Parameter

Ich habe in diesem Beispiel zwei Template-Parameter verwendet, wobei der zweite insbesondere ein int ist. int ist ein Beispiel für einen Nicht-Typ Template-Parameter. Neben int können alle ganzzahligen Typen, Fließkommatypen (C++20), aber auch Zeiger, oder Referenzen als Nicht-Typ Template-Parameter verwendet werden. Was passiert, wenn ich zwei Arrays mit unterschiedlicher Länge instanziiere?

template <typename T, int N>
class Array{
public:
int getSize() const{
return N;
}
private:
T elem[N];
};

int main() {

Array<float, 5> myArr1;
Array<float, 10> myArr2;

}

Die meisten habe es vermutlich erraten: Zwei Arrays werden instanziiert. Hier ist die entscheidende Ausgabe von C++ Insights.

Das bedeutet, dass beide Instanziierungen mit unterschiedlichen int-Werten unterschiedliche Typen erzeugen.

Wie geht es weiter?

Nach diesen ersten Schritten mit Templates, tauche ich in meinen nächsten Artikel deutlich tiefer in Funktions-Templates ein.