Basiswissen zu Templates:
nach C++ Templates von David Vandevoorde und Nicolai M. Josuttis
Funktionstemplates:
Beispiel:
template <typename T>
inline T const& max_ (T const& a, T const& b){
return a < b ? b : a;
}
#include <iostream>
#include <string>
using namespace std;
int main (){
cout << "max_(3,4): " << max_(3,4) << endl;
cout << "max_(3.5,3.6): " << max_(3.5,3.6) << endl;
cout << "max_(string(\"abc\"), string(\"cdf\") : " << max_(string("abc"), string("cdf") ) << endl;
cout << "max_(\"abc\", \"cdf\") : " << max_("abc","cdf") << endl;
//cout << "max_( 1, 2.2 ): " << max_(1, 2.2) << endl;
cout << "max_< double > (1,2.2): " << max_< double > (1,2.2) << endl;
cout << "max_(static_cast< double > (1),2.2): " << max_(static_cast < double >(1),2.2) << endl;
cout << "max_( new int, new int ): " << max_( new int, new int ) << endl;
}
- die Funktionenfamilie wird durch den Typ T bestimmt
- template <typename T> ist äquivalent zu template <class T>
- jeder Typ kann verwendet werden, der den Operator < unterstützt
- die Ersetzung des Templateparameters T durch ein Templatetype wird als Templateinstanzierung bezeichnet
- für jeden konkreten Typ (double) wird die Templatefunktion zu:
inline double const& max_ (double const& a, double const& b){
return a < b ? b : a;
- Templates werden zwei Mal auf syntaktische Richtigkeit geprüft
- der Templatecode im Allgemeinen
- die Templateinstanzierung im Besonderen (kann der Typ als Templateparameter verwendet werden)
- Konsequenzen:
- eine Templateinstanzierung kann nur erfolgen, wenn sie ihre Templatedefinition kennt
- das klassische Trennung von Übersetzen und Linken ist aufgehoben, denn "klassisch" genügt zum Übersetzen die Funktionsdeklaration
- Templates müssen im Header definiert werden
Templateparameter und Aufrufparameter :
- T in template < typename T > wird als Templateparameter bezeichnet
- a und b in max_( T const& a, T const& b ) nennt man Aufrufparameter "call parameters"
- Funktionstemplates kennen im Gegensatz zu Klassentemplates keine Defaultparameter, da sich, historisch bedingt, Templateparameter aus den Aufrufargumenten ableiten lassen sollten max_( T const& = int ) ist noch nicht möglich )
- Lösung des Codeproblems mittels zwei Templateparametern:
template <typename T1, typename T2>
inline T1 max_ (T1 const& a, T2 const& b){
return a < b ? b : a;
}
- Nachteile:
- max_( 42, 66.6 ) = 66 =! 66.6 = max_( 66.6, 42 )
- der zweite Aufrufargument muss gegenfalls konvertiert werden lokales temporäres Objekt geht "out of scope" beim Verlassen der max_ Funktion Rückgabe durch kopieren
- das Konzept zur Parameterbestimmung: Aufrufargumente Templateparameter Aufrufparameter
- Versuch mit drei Templateparametern:
template <typename T1, typename T2, typename RT>
inline RT max_ (T1 const& a, T2 const& b){
return a < b ? b : a;
}
- Nachteil: der Aufruf ist mühsam max_<int,double,double>(4,4.2)
- Umstellung der Templateparameter:
template <typename RT, typename T1, typename T2>
inline RT max_ (T1 const& a, T2 const& b){
return a < b ? b : a;
}
- Vorteil: kann mittels max_<double>(4,4.2) instanziert werden, da die Aufrufargumente die restlichen Templateparameter und Aufrufparameter bestimmen
Template Funktionen überladen:
- Überladen bezeichnet die Möglichkeit, mehrere Funktionen mit gleichem Namen aber unterschiedlichen Aufrufparametern zu definieren, sodass der Compiler entscheiden muss, welche Funktion er aufzurufen hat
- die Aufrufparameter werden auch gern als Signatur einer Funktion bezeichnet
- ein paar Beispiele:
// max von zwei ints (1)
inline int const& max_(int const& a, int const& b){
return a < b ? b : a;
}
// max von zwei beliebigen, gleichen Typen (2)
template <typename T>
inline T const& max_ (T const& a, T const& b)
{
return a < b ? b : a;
}
// max von drei beliebigen Typen (3)
template <typename T>
inline T const& max_ (T const& a, T const& b, T const& c)
{
return max_(max_(a,b), c);
}
int main()
{
max_(7, 42, 68);
// 3 mit int
max_(7.0, 42.0);
// 2 mit double
max_('a', 'b');
// 2 mit char
max_(7, 42);
// 1 mit int
max_<>(7, 42);
// 2 mit int
max_<double>(7, 42);
// 2 mit double
max_('a', 42.7);
// 1 mit int
}
- Funktionen können mit Funktionstemplates koexistieren
- Funktionstemplates unterstützen keine automatische Typekonvertierung im Gegensatz zu Funktionen (max('a',42.7 ist nur mittels einer Funktion möglich)
- "rule of thumb" zur Funktionenauswahl: die am meisten spezialisierte Funktion wird ausgewählt
- Funktionen werden Funktionstemplates vorgezogen ( max_(7, 42) => 1 )
- wenn das Funktionstemplate besser passt als die Funktion, wird das Funktionstemplate vorgezogen (max_('a','b')) 2 nicht 1, da keine Typekonvertierung notwendig
- durch günstige Auswahl der Aufrufparameter, kann die Anzahl der Templateinstanziierungen reduziert werden:
// max von zwei Pointern auf den gleichen Typ
template <typename T>
inline T* const& max (T* const& a, T* const& b){
return *a < *b ? b : a;
}
Klassentemplates
- Container aus der Standard Template Library (STL) sind typische Beispiele für Klassentemplates
Beispiel:
template <typename T>
class Stack {
private:
std::vector<T> elems;
public:
Stack();
Stack( Stack<T> const& );
Stack>T>& operator= (Stack<T> const&);
void push(T const&);
void pop();
T top() const;
bool empty() const;
};
- die Stackmethoden werden dann wie Funktionstemplates definiert die Regeln für Funktionstemplates gelten natürlich auch hier;
template <typename T>
bool Stack<T>::empty() const{ ... }
- Instanzierung der Memberfunktionen:
- die Memberfunktionen der Klassentemplates werden dann instanziert, wenn sie verwendet werden:
template <typename T>
class Foo{
public:
T t;
void set( const T& val ){ t= val.t; }
T operator*( const& T val){ return t*val.t ; }
};
int main(){
Foo< std::string > fooString;
Foo< std::string > barString;
fooString.set(std::string("foo");
barString.set(std::string("bar");
// die nächste Zeile führt zum Compilefehler
Foo < std::string> fooBarString= fooString* barString;
}
- Vorteile:
- schnellers Übersetzen
- weniger Code wird instanziert
- Templateklassen können mit Typen T instanziert werden, die nicht alle Methoden des Klassentemplates unterstützen
- Fehler zur Laufzeit des Programms
Spezialisierung von Templateklassen:
- ähnlich zu Funktionstemplates kann man auch Klassentemplates für bestimmte Typen spezialisieren
- Klassentemplates spezialisieren nun müssen auch alle Memberfunktionen spezialisiert werden
- Memberfunktionen spezialisieren nun kann die Templateklasse als ganzes nicht mehr spezialiert werden
- Beispiel zur Spezialisierung der Klassentemplates
template <typename T >
class Stack{
...
};
template <typename T>
T Stack<T>::top() const {
...
}
- wird für die Spezialisierung std::string
template <>
class Stack<std::string>{
...
};
T Stack<std::string>::top(){
...
}
- Beispiel zur Spezialisierung der Memberfunktionen für std::string:
template <typename T >
class Stack{
T top() const;
template <>
T top<std::string>() const;
};
template <typename T>
T Stack<T>::top const{
...
}
template <typename T>
template <>
T Stack<T>::top<std::string>() const{
...
}
Partielle Spezialisierung:
- Template Parameter können im Gegensatz zum vorherigen Punkt teilweise spezialisiert werden:
- allgemeine Template Parameter (1):
template <typename T1, typename T2>
class MyClass{
...
};
- gleiche Template Parameter (2):
template <typename T>
class MyClass<T,T>{
...
};
- zweiter Template Parameter ist int (3):
template <typename T>
class MyClass<T,int>{
...
};
- beide Templateparameter sind Pointer (4):
template <typename T1, typename T2>
class MyClass<T1*,T2*>{
...
};
- Anwendung:
MyClass<int,float> a; // (1)
MyClass<float,float> a; // (2)
MyClass<float,int> a; // (3)
MyClass<int*,float*> a; // (4)
MyClass<int,int>; // Fehler, da nicht eindeutig (2) versus (3)
MyClass<int*,int*>; // Fehler, da nicht eindeutig (2) versus (4)
- mögliche Auflösung der Fehler:
template <>
class MyClass<int,int>{
...
};
template <typename T>
class MyClass<T*,T*>{
...
};
- partielle Spezialisierung ist ein mächtiges Werkzeug für neue Programmiertechniken: Static Metaprogramming und Expression Templates
// berechne 3^N
template<int N>
class Pow3 {
public:
enum { result = 3 * Pow3<N-1>::result };
};
template<>
class Pow3<0> {
public:
enum { result = 1 };
};
int main()
{
std::cout << "Pow3<7>::result = " << Pow3<7>::result << '\n';
}
- Static Metaprogramming heißt diese Technik, weil zur Compilzeit (static) Code erzeugt und evaluiert wird (Metaprogramming)
- auch Werte können Templateparameter sein
- es gilt: Pow3<7>::result = 3 * Pow3<6>::result = 3 * 3 * Pow3<6>::result = ... = 3 * 3 * 3 * 3 * 3 * 3 * 3 * Pow3<0> = 3 * 3 * 3 * 3 * 3 * 3 * 3 * 1
- Expression Templates stammen aus der Matrixarithmtik
- so kann mittels Expression Templates ein Matrixoperation so geschickt aufgesplittet werden, daß folgende Operation statt 6000 Lese- und 4000 Schreiboperatone nur noch 2000 Lese- und 1000 Schreiboperationen benötigt
Array<double> x(1000),y(1000);
x= 1.2*x + x*y;
Default Template Argumente:
- im Gegensatz zu Funktionstemplate kennen Klassentemplates default Argumente
- Prominentes Beispiel:
template<class Ch, class Tr= char_traits<Ch>, class A= allocator<Ch> >
class std::basic_string{
...
};
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
int main(){
std::string str; // entspricht std::basic_string<char> str
basic_string<char,myCharTrait, myCharAllocator> myString;
}
- Flexibilität wird hier durch das Traits-Pattern erreicht
"Nontype" Template Parameter:
- Template Parameter können auch gewöhnliche Werte sein
- die Templateklasse wird dann nicht mit einem Typ, sondern mit einem Wert initialisiert
Templateklassen Parameter:
- Beispiel:
template <typename T, int MAXSIZE>
class Stack {
private:
T elems[MAXSIZE];
int numElems;
public:
Stack();
void push(T const&);
void pop();
T top() const;
bool empty() const {
return numElems == 0;
}
bool full() const {
return numElems == MAXSIZE;
}
};
...
int main(){
Stack<int,20> int20Stack;
Stack<int,40> int40Stack;
Stack<std::string,40> stringStack;
}
- jede Template Instanzierung stellt einen eigenen Typ dar
- d.h.: keine implizite cast
int20Stack=int40Stack
oder explizite castint20Stack= static_cast< int20Stack>(int40Stack)
sind möglich - default Wert sind auch zulässig:
template <typename T = int, int MAXSIZE = 200>
class Stack{
...
};
Funktionstemplate Parameter:
- typisches Beispiel in der STL:
template <typename T, int VAL>
T addValue (T const& x){
return x + VAL;
}
...
int main(){
...
std::transform ( source.begin(), source.end() ,
dest.begin(),
addValue<int,5>);
};
Einschränkungen:
- Floating-point Zahlen, Klassentypen und Objekte mit interne Bindung sind als Templateparameter nicht erlaubt:
template <double VAT>
double process( double v){
return v * VAT;
}
template <std::string name >
class MyClass{
...
};
template <char const* name>
class MyClass{
...
};
- Floating-point Zahlen werden wohl im nächsten Standard unterstützt werden
- Objekte mit interner Bindung (lokale Objekte) wie String Literale, die erst zur Laufzeit bekannt sind, können nicht zur Compilezeit zum Instanziieren verwendet werden
Weiterlesen...