C++ Templates

Funktions-Templates

Templates für Funktionen erlauben es, den Datentyp von Funktionsargumenten variabel zu halten.
Man spricht von einem Funktions-Template. Der Begriff Template-Funktion macht keinen Sinn, da das Template tatsächlich eine Menge von Funktionen definiert. Erst der Compiler erzeugt anhand der im Code vorkommenden Funktionsaufrufe (und der dabei spezifierten Argumenttypen) die eigentliche Instanziierung der Funktion aus dem Template.
Ein einfaches Funktions-Template sieht so aus:

template<class T> void f(T t) {};

Das übergebene Argument ist nun vom variablen Typ T. Auch hier ist das Schlüsselwort class synonym mit typename verwendbar. Der Compiler generiert anhand der Funktionsaufrufe und der darin übergebenen Typen alle benötigten Ausprägungen des Funktions-Templates. Auch bei mehreren Aufrufen des Funktions-Templates für denselben Datentyp wird nur eine Ausprägung im kompilierten Code gespeichert. Bei Funktions-Templates kann die Funktion einfach mit dem Argument aufgerufen werden. Eine explizite Angabe wie bei Klassen-Templates ist nicht notwendig:

int i = 5;
f(i);
f<int>(i);  // explizit: möglich aber nicht nötig!

Hier bringt der Aufruf der Funktion den Compiler zum Generieren der Funktion void f(int t). Wird die Funktion mit einem Typ aufgerufen, prüft der Compiler ob der Typ gültig ist d.h. dass die Ausprägung des Funktions-Template mit diesem Typ kompiliert werden kann. Beispiel: Die Funktion gibt das übergebene Funktionsargument via Stream Operator << aus. Das Template ist damit nur für Typen nutzbar, die diesen Operator implementieren.
Hat man keinen Aufruf der Funktion im Code kann man dennoch eine Ausprägung der Funktion im kompilierten Code explizit erzeugen lassen. Auch hier kann das Template Argument wie beim Aufruf der Funktion weggelassen werden und wird automatisch vom Compiler ergänzt:

template void f(int t);      // implizit
template void f<int>(int t); // explizit: Angabe des Typs, optional

Auch Funktions-Templates können in C++ überladen werden (wie normale Funktionen). Eine Überladung ist eine Funktion mit gleichem Namen aber anderer Sigantur (es reicht nicht wenn sich der Rückgabewert unterscheidet!).Funktions-Templates kann man genauso überladen:

template<class T> void f(T t1, T t2) {};

In C++ kann man Templates spezialisieren: voll oder teilweise. Funktions-Templates kann man nur voll spezialisieren. Das bedeutet man kann für bestimmte Typen eine eigene Implementierung der Funktion angeben:

template<> void f(char c) {}         // implizit
template<> void f<char>(char c) {}   // explizit

Merke: Eine Spezialisierung wird immer mit template <> eingeleitet.
Diese Spezialisierung ermöglicht nun die Sonderbehandlung des char Typs. Das obige allgemeine Funktions-Template ist jedoch zwingend notwendig (sonst hätte man ja auch einfach eine Funktion ohne Template-Argument für den Typ char schreiben können).

Achtung: Mit Spezialisierung von Funktions-Templates sollte man sparsam umgehen. Oft sind überladene non-template Funktionen direkter zu implementieren. Es kann außerdem dazu kommen, dass der Compiler unerwartet einen Funktionsaufruf falsch auflöst und statt einer Spezialisierung eine Überladung aufruft und vice versa.
Mehr lesen: http://www.gotw.ca/publications/mill17.htm
Mehr lesen: Wikibooks C++

Quelle: MSDN

Klassen-Templates, Polymorphie und generische Programmierung

Klassen-Template oder Template-Klasse? Klassen-Templates definieren eine Vorlage für eine Menge von Klassen z.Bsp. indem ein Datentyp variabel gehalten wird. Daher macht es keinen Sinn von einer Template-Klasse zu sprechen, da erst die spezielle Ausprägung (z.Bsp. Verwendung des Templates mit einem bestimmten Datentyp) eine Klasse erzeugt.
Die Idee dabei ist, dass das Verhalten der Klassen, die durch das Template definiert werden können sehr ähnlich ist und z.Bsp. unterschiedliche Datentypen eines Members das Verhalten nicht beeinträchtigen. Wartbarkeit und Wiederverwendbarkeit von Code ist dabei bedeutend. Man definiert Verhalten und Algorithmen einmal in einem Template und kann den Code für alle Klassen verwenden, die aus dem Template kreiert werden können.
Die STL bietet viele Beispiele dafür. Die Vector-Klasse ist als Template ausgeführt weshalb man Vektoren erzeugen kann die int, double, String etc. speichern können. Ohne Templates müsste man für jeden Datentypen eine eigene Vektorklasse definieren. Total umständlich und vielzuviel Code und Fehleranfälligkeit.
Dieser Ansatz wird als generisch bezeichnet. Der geschriebene Code ist generisch wenn er nicht speziell ist d.h. so allgemein wie möglich und mit verschiedensten Daten und Typen arbeiten kann an die er so wenig Anforderungen stellt wie möglich.

Wie werden Klassen-Templates definiert?

template<typename T>
class Foo 
{
private:
	T bar;	
};

Das Beispiel zeigt ein Template mit einem variablen Datentyp T. Statt typename kann auch class in der Definition verwendet werden. Außer einem Typ können folgende Dinge als Template-Parameter eingesetzt werden:

  • Ein integraler oder Aufzählungstyp
  • Ein Zeiger oder eine Referenz auf ein Objekt
  • Ein Zeiger oder eine Referenz auf eine Funktion
  • Ein Zeiger auf ein Klassenelement

Ein Klassen-Template wird normalerweise vollständig (inklusive Methoden) in einer C++ Headerdatei definiert. Dagegen wird für normale Klassen nur die Schnittstelle in einer Headerdatei definiert und die Implementierung der Methoden erfolgt in einer .cpp Code Datei. Zwischenzeitlich gab es für die getrennte Definition eines Templates in .h und .cpp files das Schlüsselwort export, was jedoch in C++11 wieder abgeschafft wurde. Das Problem hierbei ist, dass der Compiler in dem Moment da ein Template instanziiert wird (konkrete Ausprägung) den Code für die entstehende Klasse oder Funktion erst generieren muss. Dafür reicht nicht nur die Deklaration aus einem Header sondern der komplette Code muss für die angegebenen Template Parameter generiert werden. Deswegen einfach den Header des Templates einbinden und bei Nutzung des Templates hat der Compiler Zugriff auf den kompletten Code.

Member-Methoden in einem Klassen-Template können sowohl innerhalb als auch außerhalb der Klasse definiert werden (Dennoch in der selben Headerdatei!). In dem Fall erfolgt die Deklaration der Methode innerhalb der Klasse und die Definition außerhalb, nach der Definition der Klasse. Man schaue sich folgendes Klassen-Template eines mathematischen Vektors an:

template<class Value_T, unsigned int N>
class VectorT
{
public:
	typedef Value_T value_type;
	typedef unsigned int size_type;

	typedef Value_T* iterator;		
	typedef const Value_T* const_iterator;

	// standard constructor, init with 0
	VectorT()	
	{
	  for (int i = 0; i < N; i++) 			
            this->elements[i] = 0;
	}
			
	size_type GetNumElements() const { return N; }

	// iterators
	iterator begin()  { return this->elements; }
	iterator end()  { return this->elements + N; }
	const_iterator cbegin() const  { return this->elements; }
	const_iterator cend() const  { return this->elements + N; }
	
	VectorT& Mult(value_type s);	// hier fehlt noch die Definition!
	
protected:
	// elements in the vector
	value_type elements[N];		

	// the number of elements
	static const size_type size = N;
};

Die Methode VectorT& Mult(value_type s); muss noch definiert werden. Außerhalb des Klassen-Template (aber im selben Header) sieht das folgendermaßen aus:

// class member function defined outside the class 
template<class Value_T, unsigned int N>
VectorT<Value_T, N>& VectorT<Value_T, N>::Mult(Value_T s)
{
	for (int i = 0; i < N; i++) 		
          this->elements[i] *= s;		
	return *this;
};

Der Syntax ist kompliziert. Zunächst gibt man die Methode wie eine Template Methode an, mit Schlüsselwort template gefolgt von den template Argumenten des Klassen-Template zu der die Methode gehört. Dann muss bei jeder Verwendung des Klassen-Template-Bezeichners wiederum die Liste der Template Argumente angehängt werden! Achtung: Es werden nur die Bezeichner der Argumente benötigt! Eine erneute Deklaration mit class/typename oder unsigned int ist ungültig: VectorT<Value_T, N> statt VectorT<class Value_T, unsigned int N>.
In jeder Definition einer Methode außerhalb des Klassen-Template werden mindestens zweimal die Template-Argumente aufgeführt. Erstens in der Definition des Template und zweitens bei der Angabe des Namensraumes zu welchem die Methode gehört. Im obigen Beispiel ist die Liste sogar dreimal aufgeführt, da der Rückgabetyp der Methode ebenfalls vom Typ des Klassen-Template ist.

Achtung: Die Definition einer normalen (non-template) Member-Methode außerhalb der Klasse aber im Header ist NICHT möglich, da dabei die ODR verletzt würde. Bindet man den Header in mehreren Übersetzungseinheiten ein, hätte man eine mehrfache Definition der Methode. Bei Member-Methoden von Templates ist das jedoch kein Problem, solange die Methode keine explizite Spezialisierung ist und es min. 1 Template-Argument gibt, dass variabel ist.

Wesentlich einfacher ist die Definition der Member-Methode innerhalb des Klassen-Template:

VectorT& Mult(value_type s)
{
	for (int i = 0; i < N; i++)           
          this->elements[i] *= s;		
	return *this;
}

Der Typ VectorT kann innerhalb der Klassendeklaration ohne die Template-Argumente geschrieben werden! Der Compiler erkennt, dass es sich um den Typ des Klassen-Template handelt. Optional könnte die Definition mit den Argumenten so aussehen:

VectorT<Value_T, N>& Mult(value_type s)
{
	// the same code here
}

Klassen-Templates kann man ebenso wie Funktions-Templates spezialisieren: vollständig (explizit) oder teilweise (partiell). Explizit bedeutet, dass alle Template-Argumente angegeben werden. Teilweise Spezialisierungen lassen Argumente variabel. Das obige Vector Template kann man vollständig spezialisieren:

template<>
class VectorT<double, 5>
{
};

Beide Template-Argumente werden explizit angegeben. Achtung: Spezialisierung eines Klassen-Template ist nicht wie Vererbung! Es wird kein Code von dem Basis-Template übernommen. Man muss den kompletten Code für diese Spezialisierung neu schreiben, auch wenn sich nur eine Methode unterscheiden soll.
Eine partielle Spezialisierung sieht so aus:

template<unsigned int N>
class VectorT<float, N>
{
};

Außerdem ist es auch möglich eine normale Member-Methode explizit zu spezialisieren (partiell geht bei Methoden ja nicht!):

#ifndef MY_HEADER_H
#define MY_HEADER_H

// Header with a template

template <class T>
class TestClass
{
public:
    TestClass() {};
    ~TestClass() {};

    bool MemberFunction()
    {
        return true;
    }

};

template<>
inline bool TestClass<double>::MemberFunction()
{ return false; }

#endif

Die Methode wurde auf einen bestimmten Typen für das Argument des Klassen-Template spezialisiert. Damit ist sie in dem Sinne kein Template mehr. Der Code für diese Methode kann direkt erzeugt werden, da kein Typ variabel ist. Das Schlüsselwort inline ist hier essentiell. Der C++ Standard verbietet die mehrfache Definition einer expliziten Template-Spezialisierung (egal ob Funktion oder Klasse) innerhalb eines Programms. Ohne inline hätten wir diese Vorgabe hier schon verletzt.
Der Visual Studio Compiler (MSVC 2012) zeigt hier einen Bug. Sobald man die nicht spezialisierte Member-Methode innerhalb der Klasse definiert wird auch die spezialisierte Variante als inline deklariert, weshalb der obige Code in Visual Studio auch ohne inline kompiliert, während es mit dem GCC Compiler Fehler hagelt. Außerdem schreibt der C++ Standard vor, dass die Deklaration/Definition von expliziten Spezialisierungen im selben Namensraum geschehen muss, wie das zugehörige Template. Im obigen Fall ist es das Klassen-Template. Daher muss die spezialisierte Methode außerhalb der Klasse deklariert/definiert werden. Mit MSVC 2012 ist es wiederum möglich, die Spezialisierung wenigstens innerhalb der Klasse zu deklarieren. Möchte man die Methode nicht inline definieren, so muss im Header außerhalb der Klasse nur die Deklaration auftauchen. Die Definition kann in einer .cpp Datei erfolgen. Achtung: Mit MSVC kann die Funktion in einer .cpp Datei nur gelinkt werden wenn eine explizite Instantiierung des spezialisierten Template-Typs im Header präsent ist. Das sieht folgendermaßen aus:

#ifndef MY_HEADER_H
#define MY_HEADER_H

// Header with a template

template <class T>
class TestClass
{
public:
	TestClass() {};
	~TestClass() {};

	bool MemberFunction()
	{
		return true;
	}
};

template<>
bool TestClass<double>::MemberFunction();  // Deklaration

template class TestClass<double>; // explizite Instantiierung

#endif

Nun kann man in einer beliebigen .cpp Datei den Header einbinden und die spezialisierte Member-Methode definieren.

Abgrenzung: Bei Templates ist es teilweise verwirrend was man wo und wie deklarieren/definieren kann. Die Definition eines Funktions-Templates oder Member-Methode eines Klassen-Templates kann generell nicht in einer .cpp Datei erfolgen. Sie muss im Header erfolgen. Da es sich um ein Template handelt ist es noch kein kompilierter Objektcode und er muss an der Stelle wo er benötigt wird als template verfügbar sein, was nur mit Header-Inkludierung geht. Explizite Spezialisierungen von templates sind keine templates, sondern kompilierbarer Code, da alle Template-Argumente angegeben sind. Daher birgt hier jegliche Definition in einem Header die nicht-inline die Gefahr die ODR zu verletzen und die Funktion mehrfach zu definieren.

Mehr lesen: Stackoverflow – Explicit specialization in non-namespace scope
Mehr lesen: Stackoverflow – Member-function outside class

Templates und Operatoren

Operatoren werden oft als friend-Funktionen von Klassen implementiert. Das funktioniert auch für Klassen-Templates. Man muss jedoch darauf achten wie man einen Operator implementiert. Die Syntax ist am einfachsten wenn der Operator innerhalb der Klasse definiert wird. Am Beispiel des <<Operators sieht das so aus:

template <typename T>
class Test {
   friend std::ostream& operator<<( std::ostream& o, const Test& t ) {
      // can access the enclosing Test. Only for Test object with this type of T.
   }
};

Eine friend-Funktion ist keine Member-Methode sondern eine unabhängige Methode die Zugriff auf private Member der Klasse hat in der sie definiert wird. Wenn ich keine friend-Semantik brauche, weil die öffentliche Schnittstelle für den Operator ausreichend ist, kann ich diesen Operator nicht innerhalb der Klasse definieren. Ohne das Schlüsselwort friend, gilt der Operator als Member-Methode. In diesem Fall ist der linke Operand jedoch implizit immer vom Typ Test. Das bedeutet als non-friend kann ich den Operator nur außerhalb der Klasse definieren. Dabei muss man jedoch darauf achten, dass man keine Sicherheitslücke verursacht: Stackoverflow – Operator in template introvert or extrovert

Ein weiteres spezielles Problem kann auftreten wenn ein Operator außerhalb implementiert wird:

template<class Value_T, unsigned int N>
const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, Value_T s)		
{
	VectorT<Value_T,N> vTemp(v);
	vTemp *= s;
	return vTemp;
}

Das ist der *operator für die Skalarmultiplikation eines Vektors. Implementiert außerhalb der zugehörigen Vektorklasse und ohne friend-Eigenschaft.
Das Argument s für den Skalar ist vom selben Typ wie die Komponenten im Vektor: Für VectorT<int, N> vom Typ int, für VectorT<double, N> vom Typ double etc.

Was passiert aber nun wenn man einen Vector<double, 2> instantiiert und für ihn diesen *operator mit einem Integer Skalar aufruft? Der Compiler meckert, weil das Template-Argument mehrdeutig ist! Für den Vektor hat man double als Typ angegeben, aber beim Argument s sagt man der Value_T type wäre vom Typ Integer. Der Compiler weiß nicht für welches Value_T er nun das template instantiieren soll. Normalerweise würde ich erwarten, dass er es für double instantiiert und den übergebenen int Skalar s einfach implizit in double umwandelt, was ohne Verlust möglich ist. Da hier aber erstmal die Erzeugung bzw. Deduktion der korrekten Funktion von den Template-Parametern gefragt ist funktioniert es nicht.

Die (meiner Meinung nach) beste Lösung dafür ist es, das Argument s explizit vom gleichen Typ wie der zum Operator gehörigen VektorT::Value_T zu spezifizieren:

template<class Value_T, unsigned int N>
const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, 
              typename VectorT<Value_T, N>::value_type s)		
{
	VectorT<Value_T,N> vTemp(v);
	vTemp *= s;
	return vTemp;
}

Der angegebene value_type ist hierbei ein in VectorT definierter typedef für Value_T: typedef Value_T value_type;. Jetzt werden Vektoren die mit Ganzzahlen arbeiten auch nur mit Ganzzahlen multipliziert, wie es bei einem Vektorraum über einem mathematischen Körper der Fall ist. Erlaubt man einfach immer double als Typ für den Skalar wird ein Integer Vektor mit einem double multipliziert und das Ergebnis nach Integer umgewandelt (abgeschnitten). Beispiel: 2 * (int)2.5 = 4 vs. (int)(2 * 2.5) = 5 .