Funktionsschablonen (template-Funktionen)

Über das Überladen von Funktionen hinaus kann man in C++ noch einen Schritt weiter gehen, und sehr ähnliche Funktionen, die man überladen will, zu einer Schablone zusammenzufassen.

In der Praxis tritt oft das Problem auf, eine Funktion mehrfach programmieren zu müssen, um ein und denselben Algorithmus auf verschiedene Typen von Argumenten anzuwenden.

Ein typisches Beispiel ist das Aussuchen des kleineren Werts aus zwei Argumenten. Gegebenenfalls muß man zu diesem Zweck mehrere Funktionen definieren, die jeweils int-, unsigned int-, double-Argumente usw. erhalten und entsprechende Rückgabewerte liefern.

Neben dem Aufwand, alle Versionen auszuformulieren, hat man dann noch das Problem, die jeweils passende Funktion mit dem richtigen Namen aufzurufen. (Letzteres ließe sich gegebenenfalls durch Überladen lösen.)

Schön wäre es, wie in FORTRAN generische Funktionen zu haben, die sich im Typ der Argumente unterscheiden und dementsprechend passende Ergebnisse liefern, aber trotzdem alle gleich heißen und zudem nicht mehrfach definiert werden müssen (wie in Abschnitt Überladen von Funktionen gezeigt), sondern nur einmal -unabhängig vom tatsächlichen Typ- hingeschrieben werden. Der Compiler muß dann die passende Version auswählen.

In C kann man sich ein Makro definieren, um den kleineren zweier Werte zu finden:

// Time-stamp: "(14.11.01 20:39) min.c [Klaus Wachtler (aw38)]"

#include <stdio.h>

#define MIN(a,b)  ( (a)<(b) ? (a) : (b) )

int main()
{
  int
    i = 5,
    j = 7;
  double 
    d = 3.14,
    e = 2.7182818;

  printf( "MIN( i, j ) ist %d\n", MIN( i, j ) );
  printf( "MIN( d, e ) ist %f\n", MIN( d, e ) );

  return 0;
}
Damit hat man so etwas ähnliches wie eine generische Funktion definiert; leider ist es aber keine Funktion, auch wenn MIN in vielen Fällen sehr ähnlich verwendet werden kann.

Diese Definition hat gegenüber einer echten Funktion Vor- und Nachteile:

+
Das Makro ist schneller, da zur Laufzeit keine Parameter verwaltet werden müssen
+
Man kann das Makro ohne weiteren Aufwand für verschiedene (skalare) Datentypen verwenden und trotzdem immer mit dem selben Namen aufrufen
-
Dagegen ist das Makro unsicherer als ein Funktionsaufruf, da etwa bei MIN(i++,j++) unübersichtliche Seiteneffekte auftreten, weil einer der beiden Parameter zweifach ausgewertet wird.

C++ bietet nun die Möglichkeit, generische Funktionen (wie sie zumindest festverdrahtet von FORTRAN bekannt sind), selbst zu definieren.

Das Gegenstück zum MIN-Makro könnte in C++ so aussehen:

// Time-stamp: "(15.11.01 16:10) min.cpp [Klaus Wachtler (aw38)]"

#include <iostream>

using namespace std;

template<class T> T min( T a, T b )
{
  return a<b ? a : b;
}

int main()
{
  int
    i = 5,
    j = 7;
  double
    d = 3.14,
    e = 2.7182818;

  cout << "min( i, j ) ist " << min( i, j ) << "\n";
  cout << "min( d, e ) ist " << min( d, e ) << "\n";

  return 0;
}
Hier ist min als Schablone für Funktionen definiert, die alle den gleichen Namen (min) haben und sich im Typ der Argumente und des Rückgabewerts unterscheiden.

Bei jeder Verwendung in einem konkreten Zusammenhang setzt der Compiler einen Aufruf der passend generierten tatsächlichen Funktion ein. So werden die gefährlichen Nebeneffekte von Makros vermieden. Bei Bedarf kann man sich auch Definitionen der min-Funktion schreiben, die keine skalaren Daten vergleichen, sondern beispielsweise eigene Datentypen wie Listen oder Strings. Durch das Schlüsselwort inline wird die Funktion -sofern sie nicht zu umfangreich ist- gar nicht als eigene Funktion zur Laufzeit aufgerufen, sondern der Compiler erzeugt den entsprechenden Code an Ort und Stelle anstatt eines Funktionsaufrufs. Dadurch wird -genau wie in der Version als Makro- das Programm schneller, da zur Laufzeit keine Parameter angelegt werden müssen. Trotzdem sorgt der Compiler dafür, daß keine Seiteneffekte auftreten, beispielsweise durch die erwähnte mehrfache Auswertung von Parametern.

Die Verwendung solcher Schablonen ist nicht auf einen freien Parameter beschränkt, sondern man kann mehrere (mit Kommata getrennt) in die Dreiecksklammern schreiben. Weiterhin müssen diese Parameter gar keine Datentypen sein, sondern können auch Werte sein.

Genau genommen sind folgende Templateparameter zulässig:

Insbesondere Variablen sind nicht als Parameter zulässig.

Da alle Parameter, die kein Typ sind, als Konstanten gelten, ist es nicht zulässig, sie im Template ändern zu wollen.

Ebenso wie normale Funktionen lassen sich Templatefunktionen überladen.

Zu beachten ist, daß sich die verschiedenen generierten Funktionen auch in den Aufrufparametern unterscheiden müssen, damit der Compiler die erzeugten überladenen Funktionen auseinanderhalten kann. Deshalb müssen sich die Templateparameter auf die Typen der Parameterliste auswirken. Ohnehin gilt, daß alle Parameter der Templatedefinition auch verwendet werden müssen.



Unterabschnitte
AnyWare@Wachtler.de