C++ Kurs

Template-Spezialitäten II

Die Themen:

Member-Templates

Außer dass Klassen als Templates definiert werden können, können auch Memberfunktionen innerhalb einer Klasse als Templates realisiert werden. Diese Templates werden auch als Member-Templates bezeichnet.

Ein Member-Template wird innerhalb einer Klasse auf die gleiche Weise wie ein Funktions-Template definiert. Die Klasse selbst, die das Member-Template enthält, wird dagegen als normale Klasse definiert, d.h. sie enthält keine Template-Anweisung. Wird ein Objekt einer Klasse mit einem Member-Template definiert, so braucht hier zunächst kein Typ für das Member-Template angegeben werden, da die Klasse ja kein Template ist. Erst beim Aufruf des Member-Templates wird vom Compiler eine entsprechende Memberfunktion erzeugt. Mit Hilfe von Member-Templates kann man sich so unter Umständen eine Menge Schreibarbeit und unnötigen Code sparen, da nun nicht mehr unzählige überladene Memberfunktionen im Voraus definiert werden müssen. Außerdem enthält die Klasse letztendlich auch nur die Memberfunktionen, die auch tatsächlich aufgerufen werden.

PgmHeaderclass Any
{
   ...
   // Member-Template
   template <typename T>
   void Write (const T& val)
   {...}
};
// Objekt definieren
Any obj;
// Aufruf erzeugt entsprechende Memberfunktion
obj.Write(10);      // erzeugt Any::Write(const int&)
obj.Write(3.1);     // erzeugt Any::Write(const double&)

Wird das Member-Template nicht innerhalb der Klasse sondern außerhalb definiert, so muss der Memberfunktion nun eine Template-Anwesiung vorangestellt werden. Hierbei ist zu beachten, dass das Template-Argument nun nicht mehr nach dem Klassennamen angegeben wird, wie dies der Definition von Memberfunktionen von Klassen-Templates der Fall ist.

PgmHeaderclass Any
{
   ...
   // Member-Template deklarieren
   template <typename T>
   void Write (const T& val);
};
// Member-Template ausserhalb definieren
template <typename T>
void Any::Write(const T& val)
{...}

Steigern wir das Ganze jetzt noch etwas und sehen uns nun an, wie die Definition eines Member-Templates aussieht wenn die Klasse selbst ein Template ist.

Wird das Member-Template innerhalb des Klassen-Templates definiert, so ergibt sich keine Abweichung zum vorherigen Vorgehen. Das Einzige auf das zu achten ist, ist, dass für die Template-Argumente des Klassen-Templates und denen des Member-Templates unterschiedliche Bezeichner für die formalen Datentypen zu verwenden sind. Falls für das Klassen-Template und das Member-Template die gleichen formalen Datentypen verwendet werden, so verdeckt der Bezeichner des Member-Templates den Bezeichner des Klassen-Templates, d.h. im Member-Template können dann nicht mehr die Datentypen des Klassen-Templates verwendet werden.

PgmHeadertemplate <typename T1>
class Any
{
   ...
   // Member-Template
   template <typename T2>
   void Write (const T2& val)
   {...}
};
// Objekt definieren
Any<short> obj;
// Aufruf erzeugt entsprechende Memberfunktion
obj.Write(10);     // erzeugt: Write(const int&)
obj.Write(3.1);    // erzeugt: Write(const double&)

Etwas komplizierter sieht die Sache aus, wenn das Member-Template außerhalb des Klassen-Templates definiert wird. In diesem Fall müssen zwei Template-Anweisungen angegeben werden, eine für das Klassen-Template und eine für das Member-Template.

PgmHeadertemplate <typename T1>
class Any
{
   ...
   // Member-Template deklarieren
   template <typename T2>
   void Write (const T2& val);
};
// Member-Template ausserhalb definieren
template <typename T1>
template <typename T2>
void Any<T1>::Write(const T2& val)
{...}

BeispielHier geht's zum Beispiel.

Variadische Member-Templates

Member-Templates können auch variadisch sein, d.h. sie können eine beliebige Anzahl von Parametern erhalten. Sehen wir uns dazu zunächst das folgende Beispiel an.

PgmHeader// Beispiel variadisches Member-Templates

#include <iostream>

using std::cout;
using std::endl;

// Template-Klasse zur Ablage von beliebige pair-Datenelementen
template <typename pType>
class PairArray
{
    pType *pData = nullptr;   // Zeiger auf Datenfeld
    size_t noOfElements;      // Anzahl der Daten
    int index = 0;            // Datenfeld Index
  public:
    // Variadischer ctor
    // Erhaelt die pairs als parameter pack

    template <typename ...args>
    PairArray(args ... plist)
    {
        // Anzahl der pair Elemente berechnen
        // Division durch 2 da die Daten als pair abgelegt werden

        noOfElements = sizeof...(plist)/2;
        // Datenfeld anlegen
        pData = new pType[noOfElements];
        // Daten ins Feld uebernehmen
        ExtractRecord(plist...);
    }
    // dtor, gibt Datenfeld wieder frei
    ~PairArray()
    {
        delete [] pData;
    }
    // Ausgabe der Daten
    void Print()
    {
        for (int i=0; i<noOfElements; i++)
            cout << pData[i].first << ' ' << pData[i].second << endl;
        cout << endl;
    }
  private:
    // Erzeugt ein pair und fuegt dieses zum Feld hinzu
    template <typename ftype, typename stype>
    void ExtractRecord(ftype first, stype second )
    {
        pData[index++] = std::make_pair(first, second);
    }
    // Extrahiert die ersten beiden Element des parameter packs,
    // erzeugt daraus ein pair und fuegt dieses zum Feld hinzu
    // Das restliche parameter pack wird weitergegeben (Rekursion!)

    template <typename ftype, typename stype, typename ...plist>
    void ExtractRecord(ftype first, stype second, plist... rest)
    {
        pData[index++] = std::make_pair(first,second);
        ExtractRecord(rest...);
    }
};

// main() Funktion
int main()
{
    // Erstellt ein Feld mit der Zuordnung von Postleitzahlen zu Ort
    using cityPair = std::pair<long,std::string>;
    PairArray<cityPair> cities(21029,"Hamburg",80804,"Muenchen",
                               70376,"Stuttgart");
    cities.Print();
    // Erstellt ein Feld fuer die Umrechnung von Grad nach Bogenmass
    using d2rPair = std::pair<int,double>;
    PairArray<d2rPair> deg2rad(45,0.785, 90,1.571, 135,2.356,
                               180,3.142, 225,3.927);
    deg2rad.Print();
}

Das Klassen-Template PairArray dient zur Ablage von pair-Elementen in einen Feld. Die Daten der jeweiligen pair-Elemente werden hierbei direkt an den Konstruktor der Klasse übergeben. Da beim Entwurf der Klasse aber nicht bekannt ist, wie viele pair-Elemente im Objekt ablegt werden, wird ein variadischer Konstruktor benötigt. Dieser ermittelt mit Hilfe von sizeof...() aus dem parameter pack die Anzahl der pair Elemente und legt ein entsprechend großes pair-Feld an. Anschließend wird das parameter pack an die zweite Memberfunktion ExtractRecord() übergeben, die die ersten beiden Daten aus dem parameter pack entfernt und diese in einem pair ablegt. Das so erzeugte pair wird dann im Feld abgelegt und die Memberfunktion ExtractRecord() erneut mit dem verbliebenen parameter pack aufgerufen. Diese Rekursion wird beendet, wenn im parameter pack nur noch zwei Werte übrig bleiben, da in diesem Fall die erste Memberfunktion ExtractRecord() aufgerufen wird, welche diese letzten beiden Daten noch im Feld ablegt.

Template Spezialisierungen

Angenommen, wir haben ein allgemeines Klassen-Template für die Verarbeitung von beliebigen Daten definiert. Nur für einen Datentyp passt dieses Klassen-Template nicht so richtig, sei es aufgrund von irgendwelchen Einschränkungen, die dieser Datentyp besitzt, oder aus Geschwindigkeitsgründen. Was also tun? Wir könnten für diesen Fall z.B. eine eigene Klasse definieren, die genau zu diesem Datentyp passt. Aber es geht auch anders, durch spezialisieren des Klassen-Templates für diesen einen Datentyp.

Nachfolgend ist ein solcher Fall einmal beispielhaft dargestellt. Das Klassen-Template Store dient zur Ablage eines beliebigen Datums. Für die Addition eines Wertes zum abgelegten Datum, besitzt das Klassen-Template die Memberfunktion Add(...). Werden nun Objekte von diesem Klassen-Template definiert die numerische Daten verarbeiten, so ist alles noch in Ordnung. Wenn nun aber ein Objekt definiert wird, das anstelle eines numerischen Datums zum Beispiel einen C-String (char-Zeiger) ablegt, so würde die Add(...) Memberfunktion eine Zeiger-Addition durchführen und nicht, wie wahrscheinlich beabsichtigt, die C-Strings einander anfügen.

PgmHeader// Klassen-Template
template <typename T> class Store
{
   T data;
   ...
   Add(const T& param)
   {
      data += param;
   }
};
// Objekte definieren
Store<int> intObj;
intObj.Add(10);               // int Addition
Store<char*> cptrObj;
cptrObj.Add("Text hinzu");    // char* Addition

Für einen solchen Fall ist das Klassen-Template für den Datentyp char* zu spezialisieren.

Um ein Klassen-Template für einen bestimmten Datentyp zu spezialisieren, bleibt die spitze Klammer in der Template-Anweisung des spezialisierten Templates leer. Der Datentyp, für den das Klassen-Template spezialisiert werden soll, wird nach dem Namen des Klassen-Templates in spitzen Klammern angegeben. Das so spezialisierte Klassen-Template hat aber außer dem Namen mit dem allgemeinen Klassen-Template nichts mehr gemeinsam, d.h. es muss nicht die gleichen Eigenschaften und Memberfunktionen wie das allgemeine Klassen-Template besitzen.

PgmHeader// allgemeines Klassen-Template
template <typename T>
class Store
{...};
// spezialisiertes Klassen-Template
template <>
class Store<char*>
{...};
// Objekte definieren
Store<int> intObj;
intObj.Add(10);          // allg. Template-Objekt
Store<char*> cptrObj;
cptrObj.Add("huhu");     // char* Template-Objekt
HinweisSind nur eine oder zwei Memberfunktionen des allgemeinen Templates für einen bestimmten Datentyp nicht geeignet, so können alternativ diese Template-Memberfunktion explizit überschrieben werden.

BeispeilHier geht's zum Beispiel.

Partielle Template-Spezialisierung

Steigern wir die Spezialisierung von Templates noch etwas. Besitzt ein Template mehrere Template-Argumente, so kann ein solches Template auch für ein oder mehrere dieser Template-Argumente spezialisiert werden.

Angenommen, mittels einer Template-Klasse sollen beliebige Daten byteweise in einen Stream übertragen werden. Dazu besitzt das Template zwei Template-Argumente: eines für den Datentyp des Datums und eines für den Stream. Soll nun die Übertragung byteweise erfolgen, so muss bei numerischen Daten die Zerlegung des Datums in Einzelbytes selbst vorgenommen werden, da standardmäßig keine solche Funktionalität zur Verfügung steht. Wird jedoch ein string übertragen, so kann auf die einzelnen Bytes des strings direkt über den string-Iterator zugegriffen werden. Für den Fall, dass ein string in einen Stream übertragen werden soll, könnte eine eigene Klasse oder ein spezialisiertes Klassen-Template definiert werden.

Aber es geht auch wieder einmal anders, durch eine partielle Template-Spezialisierung. Im vorherigen Abschnitt haben wir bereits erfahren, dass sich Klassen-Templates für bestimmte Datentypen spezialisieren lassen. Besitzt nun ein Klassen-Template nicht nur ein Template-Argument sondern mehrere, so kann für jedes dieser Template-Argumente das Template spezialisiert werden. Bei dieser partiellen Template-Spezialisierung bleibt dann aber mindestens eines dieser Template-Argumente erhalten, d.h. es wird nicht spezialisiert.

Die partielle Template-Spezialisierung erfolgt in der Art, dass bei der Template-Deklaration des spezialisierten Templates nur noch die formalen Datentypen aufgeführt werden, die allgemein gültig sind. Anschließend werden dann nach dem Klassennamen des Templates in spitzen Klammern alle formalen Datentypen und die Datentypen aufgelistet, für die diese partielle Template-Spezialisierung gelten soll. Im untenstehenden Beispiel wird das Klassen-Template Any für den Fall spezialisiert, dass der Datentyp des zweiten Template-Arguments vom Typ string ist. Der Datentyp des ersten Template-Arguments kann weiterhin beliebig sein.

PgmHeader// Allgemeines Klassen-Template
template <typename T1, typename T2>
class Any
{
   T1 out;
   void Transmit(const T2& data)
   {
      out << ... // In Einzelbytes zerlegen
   }
};
// Partiell spezialisiertes Klassen-Template für den Datentyp string
template <typename T1>
class Any<T1, string>
{
   T1 out;
   void Transmit(const string& data)
   {
      out << ... // Bytes über Iterator
   }
};
// Objekt Definitionen
Any<ostream,int> iTrans(cout);
Any<ostream,string> sTrans(outFile);

BeispielHier geht's zum Beispiel.

Partielle Template-Spezialisierung und non-type Argumente

Außer für Datentypen lassen sich Templates auch für bestimmte Werte von non-type Template-Argumente spezialisieren. Solche Spezialisierungen werden zum Beispiel dann eingesetzt, wenn für bestimmte Werte der im allgemeinen Klassen-Template verwendete Algorithmus vereinfacht werden kann um die Ausführungsgeschwindigkeit des Programms zu erhöhen.

Um ein Klassen-Template für einen bestimmten Wert eines non-type Arguments zu spezialisieren, wird in der Template-Anweisung nur noch das Template-Argument, also nicht mehr das non-type Argument, angegeben. Zusätzlich muss dann nach dem Klassennamen in spitzen Klammern das Template-Argument sowie der Wert des non-type Arguments, für den das spezialisierte Klassen-Template verwendet werden soll, angegeben werden

Im Beispiel wird das Klassen-Template Any für den Fall spezialisiert, dass für das non-type Argument der Wert 32 angegeben wird.

PgmHeader// Allgemeines Klassen-Template
template <typename T, int SIZE>
class Any
{...};
// Partiell spezialisiertes Klassen-Template für den Fall SIZE=32
tempalte <typename T>
class Any<T,32>
{...};
// Objekte Definitionen
Any<short,5> obj1;     // Allg. Klassen-Template
Any<float,32> obj2;    // Spezialisiertes Klassen-Template

Enthält eine Template-Deklaration zwei non-type Argumente, so kann das Klassen-Template sogar für den Fall spezialisiert werden, dass beide non-type Argumente den gleich Wert besitzen, und dies sogar unabhängig vom eigentlichen Wert.

So wird im nachfolgenden Beispiel Any für den Fall spezialisiert, dass das Template-Argument row den gleichen Wert besitzt wie das Template-Argument col. Dabei ist zu beachten, dass innerhalb der Template-Deklaration nun nur noch ein non-type Argument steht, aber nach dem Klassennamen in der spitzen Klammer beide non-type Argumente aufgeführt werden müssen.

PgmHeader// Allgemeines Klassen-Template für unterschiedliche row, col Werte
template <typename T, int row, int col>
class Any
{...};
// Partiell spezialisiertes Klassen-Template
// für den Fall, dass row gleich col ist

tempalte <typename T, int size>
class Any<T,size,size>
{...};
// Objekte Definitionen
Any<short,3,5> obj1;     // Allg. Klassen-Template
Any<float,4,4> obj2;     // Spezialisiertes Klassen-Template

Zu guter Letzt kann ein Klassen-Template sogar dann spezialisiert werden, wenn überhaupt keine Template-Argumente in der Template-Anweisung vorhanden sind.

Die nachfolgende Klassen-Template Image soll zum Zeichnen von Grafiken dienen. Für den Fall, dass eine Grafik mit der Größe 32x32 gezeichnet werden soll, kann zum Beispiel ein optimierter Algorithmus zum Zeichnen der Grafik verwendet werden. Beachten Sie, dass die Template-Deklaration des Klassen-Templates nun leer ist.

PgmHeader// Allgemeines Klassen-Template
template <int width, int height>
class Image
{...};
// Partiell spezialisiertes Klassen-Template für den Fall width=height=32
template <>
class Image<32,32>
{...};
// Objekte Definitionen
Image<640,480> obj1;     // Allgemeines Klassen-Template
Image<32,32>   obj2;     // Spezialisiertes Klassen-Template

BeispielHier geht's zum Beispiel.

Template-Template-Parameter

Nähern wir uns jetzt langsam dem Höhepunkt der Template-Spezialitäten und sehen uns die sogenannten Template-Template-Parameter (TTP) an.

Wie der Name schon sagt, sind TTPs Parameter die selbst wiederum Templates sind. Innerhalb der Standard-Bibliothek werden solche TTPs sehr häufig eingesetzt, zum Beispiel zur Reservierung von Speicher. Aber sehen wir uns dieses Einsatzgebiet von TTPs auch gleich anhand eines praktischen Beispiels an.

Vorgegeben sei das Klassen-Template Store zum Abspeichern von Daten. Der für das Datum erforderliche Speicherplatz soll je nach Anforderung entweder mittels des new-Operators oder mit Hilfe der malloc(...)-Funktion reserviert werden. Dazu erhält die Template-Deklaration einen bool-Wert als non-type Parameter. Besitzt der Parameter den Wert true, so erfolgt die Speicherreservierung mittels new und ansonsten mittels malloc(...).

PgmHeader// Klassen-Template
template <typename T, bool newAlloc>
class Store
{
   T* data;
   Store(...)
   {
      if (newAlloc)
         data = new T;
      else
         data = static_cast<T*>(malloc(sizeof(T)));
         ...
   }
   ...
};
// Objekt Definitionen
Store<int,true>    intNew;
Store<float,false> floatMalloc;

Da die beiden verwendeten Verfahren zur Reservierung von Speicher auch noch an anderen Stellen eingesetzt werden sollen, werden sie aus dem ursprünglichen Klassen-Template herausgelöst und als eigenständige Klassen-Templates definiert.

PgmHeader// Klassen-Templates zum Reservieren von Speicher
template <typename T>
struct NewAlloc    // Reserviert mit new
{...};
template <typename T>
struct MallocAlloc // Reserviert mit malloc()
{...};
// Klassen-Template für Daten
template <typename T, ???>
class Store
{...};
// Objekt Definitionen
Store<int,???>    intNew;
Store<float,???>  floatMalloc;

Bleibt damit aber die Frage offen, wie das neue Klassen-Template NewAlloc bzw. MallocAlloc für die Reservierung von Speicher an das Klassen-Template Store übergeben wird und wie dann letztendlich Objekte vom Typ Store definiert werden.

Wie die Überschrift dieses Abschnitts vermuten lässt, wird das neue Klassen-Template NewAlloc bzw. MallocAlloc wahrscheinlich als Template-Parameter übergeben werden.

Beginnen wir mit der Template-Anweisung eines Templates, das einen Template-Parameter besitzt. Da das Template-Argument nun selbst ein Template ist, muss dies bei der Template-Anweisung wie nachfolgend dargestellt angegeben werden. Und hier gilt zu beachten, dass beim <typename> des TTPs kein formaler Datentyp für den einzusetzenden Datentyp steht.

Wenn wir nun weiter davon ausgehen, dass die beide Klassen-Templates für die Speicherreservierung die Memberfunktion Create() enthalten um den Speicher anzufordern, so kann diese Memberfunktion wie angegeben aufgerufen. Der Platzhalter T gibt hier den Datentyp an, für den Speicher zu reservieren ist.

PgmHeader// Klassen-Templates für Speicherreservierung
// Sowohl NewAlloc als auch MallocAlloc enthalten die
// Memberfunktion Create() (hier nicht dargestellt)

template <typename T> struct NewAlloc;
template <typename T> struct MallocAlloc;

// Klassen-Template für Daten
template <typename T, template <typename> class TAlloc>
class Store
{
   T* data;
   Store(...)
   {
      // Speicher reservieren!
      data = TAlloc<T>::Create();
   }
   ...
};

Ja es ist sogar möglich für den TTP ein Default-Klassen-Template vorzugeben. Dazu wird innerhalb der Template-Anweisung nach dem Namen des TTPs der Zuweisungsoperator gefolgt vom Namen des Default-Templates angegeben.

PgmHeader// Klassen-Templates für Speicherreservierung
template <typename T> struct NewAlloc;
template <typename T> struct MallocAlloc;

// Klassen-Template für Daten
template <typename T, template <typename> class TAlloc=NewAlloc>
class Store
{...};

Bei der Definition eines Objekts eines Klassen-Templates mit einem TTP muss das als TTP zu verwendende Klassen-Template innerhalb der spitzen Klammern mit angegeben. Besitzt das Klassen-Template ein Default-TTP, so kann diese Angabe selbstverständlich auch entfallen.

PgmHeader// Objekt Definitionen
Store<int, NewAlloc> newInt;
Store<Demo, MallocAlloc> mallocObj;
Store<float> newFloat;     // Verwendet Default-Template

Im obigen Beispiel werden drei Objekte der Template-Klasse Store definiert. Das erste Objekt newInt speichert einen int-Wert ab und die Speicherreservierung erfolgt über das Klassen-Template NewAlloc. Im zweiten Objekte mallocObj wird ein Objekt der Klasse Demo abgespeichert, dessen Speicher über das Klassen-Template MallocAlloc reserviert wird. Und zu guter Letzt verwendet das Objekt newFloat das Default-Klassen-Template für die Speicherreservierung um einen float-Wert abzulegen.

Gingen die bisherigen Beispiele immer davon aus, dass das Klassen-Template des TTP den gleichen Datentyp verwendet wie das eigentliche Klassen-Template (d.h. NewAlloc bzw. MallocAlloc verwenden den gleichen formalen Datentyp wie Store), so muss dies nicht immer zwangsläufig so sein. Im Beispiel unten verwendet das Klassen-Template Any den formalen Datentyp T1 und das Klassen-Template TP den formalen Datentyp T2. Wird nun Objekt vom Typ Any definiert, so sind natürlich beide Datentypen und das Klassen-Template des TTPs zu spezifizieren.

PgmHeader// Klassen-Template mit TTP
template <typename T1, typename T2, template <typename> class TP>
class Any
{
   T1 data;
   DoSome(...)
   {
      TP<T2>::AnyMethode();
   }
   ...
};
// Objekt Definition
Any<short, float, TTPClass> obj;

BeispielUnd hier geht's zum Beispiel.

Template als Compilezeit Ausdruck

Und nun folgt die Krönung und auch gleichzeitig der Abschluss der Template-Programmierung: Templates als Compilezeit Ausdrücke.

Mit der Einführung von Templates ergab sich eine, am Anfang nicht beabsichtigte, Möglichkeit, bereits zur Compilezeit Anweisungen ausführen zu lassen. Dadurch wirkt der Compiler quasi wie ein Interpreter, der das Ergebnis seiner 'Berechnungen' direkt in den ausführbaren Code einbauen kann.

Sehen wir uns dies an dem klassischen, aber nicht sehr praxisnahen, Beispiel der Fakultätsberechnung an.

PgmHeader// Beispiel für Compile-Time Ausdrücke
// Max. berechenbar ist 12! da ansonsten der Werteberich
// des unsigned long überschritten wird
// Sie wissen vielleicht noch, dass für non-type
// Template-Parameter keine Gleitkommazahlen zulässig sind!

#include <iostream>

using std::cout;
using std::endl;

// Allgemeines Klassen-Template für die Fakultätsberechnung
// Innerhalb des Klassen-Template wird ein neues Klassen-Template
// instanziiert das als Template-Argument den bisherigen Wert-1
// erhält

template <int val>
struct Fakultaet
{
   static const unsigned long value = val*Fakultaet<val-1>::value;
};
// Spezialisiertes Klassen-Template für Fakultät(0)
// Dies ist der Ausgang aus der Schleife der Template-Instanziierungen

template <>
struct Fakultaet<0>
{
   static const unsigned long value = 1;
};

// main() Funktion
int main()
{
   cout << Fakultaet<5>::value << endl;
}

Was passiert hier nun? Zunächst soll in main(...) die Eigenschaft value des Templates Fakultaet<5> ausgegeben werden. Also erzeugt der Compiler zunächst eine Instanz dieses Templates:

struct Fakultaet
{
   static const unsigned long value = 5*Fakulaet<4>::value;
}

Die obigen Literale 5 und 4 wurden aus dem Wert des non-type Template-Parameter val abgeleitet

Für die Berechnung von value benötigt er aber eine weitere Instanz dieses Templates, diesmal aber vom 'Typ' Fakultaet<4>:

struct Fakultaet
{
   static const unsigned long value = 5*4*Fakulaet<3>::value;
}

Und dieses Spiel wiederholt sich so lange, bis der Wert des Template-Arguments schließlich 0 ist. Für diesen Wert ist das Template spezialisiert und liefert als value immer den Wert 1. Gleichzeitig wird damit die 'Schleife' der Template-Instanziierungen beendet und das endgültige Ergebnis kann nun in die cout-Anweisung in main(...) eingesetzt werden. Beachten Sie, dass diese Berechnung komplett zur Compiler-Zeit vorgenommen wird, da sie letztendlich nur aus der Multiplikation von Konstanten besteht! Wenn Sie mit einem Debugger sich die cout-Anweisung einmal ansehen werden Sie feststellen, dass dort tatsächlich der Wert 120 (=5!) eingesetzt wurde.

Dies ist zugegeben ein recht einfaches, aber nicht praxisnahes, Beispiel. Das nachfolgende Beispiel ist da schon etwas näher an der Praxis.

Da sich der Datentyp char (nicht signed-char oder unsigned-char!) in der Regel über Compiler-Optionen auf signed oder unsigned einstellen lässt, kann es unter Umständen zu Fehlern kommen, wenn das gleiche Programm mit unterschiedlichen Compiler-Optionen übersetzt. Das Klassen-Template WrongCharFormat hilft einen solchen Fehler schon zum Zeitpunkt des Übersetzens des Programms aufzudecken.

HinweisUnter CodeBlocks können Sie den Datentyp eines char über die Project build options wie nachfolgend dargestellt einstellen:
GCC char type
PgmHeader// Beispiel für Typueberpruefung zur Compile-Time

// BCC32: char = signed (default)
//               unsigned (Compile-Switch -K)
// VC++ : char = signed (default)
//               unsigned (Compile-Switch /J)
// MinGW: char = signed (default)
//               unsigned (Compile-Switch -funsigned-signed)


#include <iostream>
#include <limits>

using std::cout;
using std::endl;

// Vorwärtsdeklaration eines Templates das als
// Template-Argument einen bool-Datentyp besitzt

template <bool> struct WrongCharFormat;
// Vollständiges Template für den Fall dass der bool-
// Template-Parameter true ist.

template<> struct WrongCharFormat<true>
{};
// Funktion zur Typüberprüfung des char-Datentyps auf
// signed char.
// Dazu wird ein Objekt des Klassen-Templates WrongCharFormat
// instanziiert, dessen Template-Parameter nur dann true
// ist, wenn char einem signed char entspricht. Im anderen
// Fall wird versucht ein Objekt des allgemeinen Klassen-Templates
// WrongCharFormat zu erstellen, das aber aufgrund der
// unvollständigen Template-Definition fehlschlägt und
// damit zur Compile-Time einen Fehler erzeugt

inline void CheckCharFormat()
{
   WrongCharFormat<std::numeric_limits<char>::is_signed == true>();
};

int main()
{
   // char-Format auf signed char testen
   CheckCharFormat();

}

Durch Abwandeln des Template-Arguments in der Funktion CheckCharFormat() lassen sich mit diesem Template auch andere Datentypen zur Compilzeit abprüfen.

So, damit soll es jetzt mit der Deklaration und Definition von Templates genug sein! Das Thema 'Templates als Compilezeit Ausdruck' ist sehr ergiebig. So lassen sich mit solchen Template-Konstruktionen u.a. Schleifen und Verzweigungen programmieren. Wenn Sie mehr darüber wissen sollen, so sollten Sie im Internet einmal nach dem Stichwort 'template metaprograms' suchen.