Die Idee eines Promotion Trait ist recht einfach: Bestimme den Rückgabetyp des Funktions-Templates abhängig von seinen Eingabeargumenten dadurch, dass für jede Typkombination ein Rückgabetyp hinterlegt ist.
Generische add-Funktion
An einer generischen Funktion add
, die zwei Werte addiert, lässt sich diese Technik einfach darstellen. Das Funktions-Template besitzt die folgende Definition.
??? add(T1 first, T2 second){ return first + second; }
Um generisch zu sein, muss der Rückgabetyp von den Argumenten abhängen. Das lässt sich aber nicht allgemein bestimmen, da für zwei Argumente vom Typ double
und int
der Rückgabetyp double
, hingegen für zwei Typen long long
und int
der Rückgabetyp long long
sein sollte. In Listing E.1 wird der Rückgabetyp aus dem Funktions-Template extrahiert und über ein typdef
zur Verfügung gestellt. Virtualität zur Compile-Zeit sorgt dafür, dass jedes Funktions-Template zur Laufzeit seinen richtigen Rückgabetyp besitzt.
promotionTrait.cpp
01 #include <iostream> 02 03 template<typename T1, typename T2> 04 struct PromotionTrait{ 05 }; 06 07 template<typename T> 08 struct PromotionTrait<T,T>{ 09 typedef T ResultT; 10 }; 11 12 template<> 13 struct PromotionTrait<int,long long int>{ 14 typedef long long int ResultT; 15 }; 16 17 template<> 18 struct PromotionTrait<long long int,int>{ 19 typedef long long int ResultT; 20 }; 21 22 template<> 23 struct PromotionTrait<int,double>{ 24 typedef double ResultT; 25 }; 26 27 template<> 28 struct PromotionTrait<double,int>{ 29 typedef double ResultT; 30 }; 31 32 template<typename T1, typename T2> 33 inline typename PromotionTrait<T1,T2>::ResultT add( T1 first, T2 second){ 34 return first + second; 35 } 36 37 int main(){ 38 39 std::cout << std::endl; 40 41 std::cout << "add(1,1)= " << add(1,1) << std::endl; 42 std::cout << "add(1,2.1)= " << add(1,2.1) << std::endl; 43 std::cout << "add(1000LL,5)= " << add(1000LL,5) << std::endl; 44 45 std::cout << std::endl; 46 47 }
Betrachten wir zuerst das Promotion Trait PromotionTrait
in Zeile 3. Es ist nicht vollständig implementiert, da ihm der typedef
für den Rückgabetyp fehlt. Dieses primäre Template, das keine Einschränkung bezüglich seiner Argumente besitzt, schreibt die C++-Syntax vor. Es folgen die Spezialisierungen des Templates. Die Spezialisierung in Zeile 7 wird verwendet, wenn die zwei Argumente vom gleichen Typ sind. Ist das der Fall, wird genau der Typ der Eingabeargumente als Rückgabetyp über typedef T ResultT
zurückgegeben. Alle anderen vollständigen Spezialisierungen werden genau dann verwendet, wenn jeweils die Argumenttypen den Parametertypen entsprechen. So wird durch add(1,2.1)
(Zeile 42) das Template struct PromotionTrait<int,double>
(Zeile 22) angewandt. Dieses Template gibt über den Aufruf typename PromotionTrait<T1,T2>::ResultT
den Typ double
zurück.
Die Ausgabe zeigt das beschriebene Verhalten.
Für jeden potenziellen Datentyp eine Spezialisierung von PromotionTrait
vorzuhalten, hat zwei entscheidende Nachteile:
Die Kombinationsmöglichkeiten steigen in der Größenordnung n*n, wenn n die Anzahl der Datentypen ist.
Für jeden neuen Datentyp müssen alle Spezialisierungen zweimal implementiert werden.
Das geht deutlich einfacher mit decltype
(siehe Kernsprache).