Template Funktionen

Ein Funktionstemplate erlaubt es eine Funktionalität für verschiedene Typen bereitzustellen. Man könnte auch sagen, das eine Template Funktion eine Familie von Funktionen darstellt.

Definition einer Template Funktion

Wie im Kapitel Was sind Templates bereits erwähnt definiert man Templates wie folgt:

// Pseudocode
template <type parameters>
{function definition}

In diesem Kapitel wird genauer darauf eingegangen was passiert wen der Compiler auf ein Template trifft.

Das folgende Beispiel, welches das Minimum zweier Variablen ermittelt, dient uns als Veranschaulichung für die Erklärungen in diesem Kapitel.

Beispiel

// File minimum.hpp
template <typename Type>
inline Type const& minimum(Type const& a, Type const& b)
{
   return a < b ? a : b;
}

Nutzung von Template Funktionen

Das folgende Beispielprogramm zeigt, wie man die Funktion minimum() verwendet

#include "minimum.hpp"
#include <string>
#include <iostream>
 
int main(void)
{
  std::cout << "minimum(\"aa\", \"ab\"): "  << minimum(std::string("aa"), std::string("ab")) << std::endl;
  std::cout << "minimum(9, 7):           "  << minimum(9, 7) << std::endl;
 
  return 0;
}

Die Ausgabe ist wie erwartet:
minimum(„aa“, „ab“): aa
minimum(9, 7): 7

Instantiierung eines Templates

Sobald der Compiler auf einen Aufruf der Templatefunktion stößt, wird das Template für den aufgerufen Typ instantiiert (im Sinne vom Erzeugen der richtigen Funktion). Das heißt, dass der Compiler für den Aufruf

minimum(std::string("aa"), std::string("ab"));

eine Funktion

inline std::string const& test(std::string const& a, std::string const& b)
{
   // Inhalt wie oben im Template
}

generiert. Das selbe passiert beim Aufruf für die Funktion mit den Integer werten.

Bestimmung der Argumente

Wenn wir minimum() aufrufen, bestimmt der Compiler anhand der Argumente die wir an minimum() übergeben wie der Templateparameter auszusehen hat. Übergeben wir also zwei Integer an minimum() ersezt der Compiler den Template Parameter Type mit int.

Allerdings ist zu beachten, dass bei dieser Art der Argumentbestimmung keine automatische Typkonvertierung erlaubt ist. Das heißt dass der folgende Code nicht erlaubt ist.

// ...
std::cout << "minimum(7, 4.2): " << minimum(7, 4.2) << std::endl; //Fehler: 7 und 4.2 sind nicht vom gleichen Typ
// ...

Um dieses Problem zu umgehen gibt es folgende Möglichkeiten:

  • Man castet beide Argumente so dass sie vom gleichen Typ sind.
  ... minimum(static_cast<double>(7), 4.2) ...
 
  • Man qualifiziert den Aufruf durch direkte Angabe des Template Parameters
  ... minimum<double>(7, 4.2) ...
 
  • Man ändert die Template Parameter Liste damit verschiedene Typen erlaubt sind

Template Parameter

Funktionstemplates haben zwei Arten von Parametern

  1. Die Templateparameter
  2. Die Funktionsparameter

Um zwei Typen für unsere Funktion minimum() zuzulassen erweitern wir unsere Templatefunktion wie folgt:

template <typename Type1, typename Type2>
inline Type1 minimum(Type1 const& a, Type2 const& b)
{
  return a < b ? a : b;
}

Es fällt auf das bei dieser Funktion der Rückgabewert von einer Referenz auf einen Wert geändert wurde. Das kommt daher, weil bei der Konvertierung zwischen Type1 und Type2 ein temporäres Objekt erzeugt wird. Die Rückgabe von Temporären Objekten als Referenz führt zu undefiniertem Verhalten was inakzeptabel ist.

Außerdem bestimmt der erste Parameter auch den Rückgabewert der Funktion was für den Aufrufer der Funktion unerwartet sein kann, wenn etwa bei einem Integer - Double Vergleich trotz des kleineren Double Wertes der Double Wert als Integer Wert zurückkommt, wie in folgendem Beispiel.

... minimum(6, 3.2) ... // liefert 3 als Ergebnis


Daher ist es nötig das Funktionstemplate erneut anzupassen. Es erhält als weiteren Template Parameter den Typ des Rückgabewertes.

template <typename ReturnType, typename Type1, typename Type2>
inline ReturnType minimum(Type1 const& a, Type2 const& b)
{
  return a < b ? a : b;
}

Der Typ des Rückgabewertes wird an den Anfang der Template Parameter Liste geschrieben, weil er nicht automatisch ermittelt werden kann da er nicht in der Parameterliste vorkommt. Die beiden anderen Parameter können wie gewohnt vom Compiler anhand der Funktionsparameter ermittelt werden.

Der Aufruf gestaltet sich daher wie folgt:

... minimum<double>(6, 4.2) ... // mimimum liefert einen double zurück (4.2)

Überladen von Template Funktionen

Auch das Überladen von Template Funktionen ist möglich. Das ist sinnvoll, wenn man für bestimmte Typen eine gesonderte Behandlung benötigt. Bei Zeigern will man normalerweise nicht die Adressen sondern die Werte hinter den Adressen vergleichen. Daher erweitert man die Minimum Funktion um eine Überladung.

// Für alle normalen Typen
template <typename Type>
inline Type const& minimum(Type const& a, Type const& b)
{
   return a < b ? a : b;
}
 
// Für Zeiger
template<typename Type>
inline Type const& minimum(Type* const& a, Type* const& b)
{
	return *a < *b ? *a : *b;
}

Ruft man die Funktion mit Zeigern auf

// ...
int a = 72, b = 48;
std::cout << "mimum(&a (points to 72), &b (points to 48)): " << minimum(&a, &b) << std::endl;
// ...

übersetzt der Compiler das Zeiger Template, da dessen Signatur am besten zum Aufruf passt.

Kapitel

Die folgenden Kapitel geben einen Einblick in die Programmierung mit Templates

  1. Template Funktionen