Funktions-Templates
Ein Funktions-Template ist eine Familie von Funktionen. In diesem Beitrag möchte ich tiefer in das Thema eintauchen.
Ein Funktions-Template ist eine Familie von Funktionen. In diesem Beitrag möchte ich tiefer in das Thema eintauchen.

Hier ist eine kurze Erinnerung, um alle auf den gleichen Wissensstand zu bringen.
Wenn ein Funktions-Template wie max
für int
und double
instanziiert wird,
template <typename T>
T max(T lhs,T rhs) {
return (lhs > rhs)? lhs : rhs;
}
int main() {
max(10, 5);
max(10,5, 5,5);
}
erzeugt der Compiler ein vollständig spezialisiertes Funktions-Template für int
und double
: max<int>
und max<double>
. Der generische Teil ist in beiden Fällen leer: template<>.
Dank C++ Insights kann ich tiefere Einsichten anbieten.

Jetzt möchte ich in die Details eintauchen. Was passiert, wenn ich Funktions-Templates und gewöhnliche Funktionen (kurz: Funktionen) verwende?
Überladen von Funktions-Templates und Funktionen
Dazu verwende ich erneut die Funktion max
. Diesmal instanziiere ich sie für float
und double
. Dazu stelle ich eine Funktion max
bereit, die auch doubles nimmt.
Hier ist mein nächstes Beispiel:
template <typename T>
T max(T lhs,T rhs) {
return (lhs > rhs)? lhs : rhs;
}
double max(double lhs, double rhs) {
return (lhs > rhs)? lhs : rhs;
}
int main() {
max(10.5f, 5.5f); // (1)
max(10.5, 5.5); // (2)
}
Dabei drängt sich eine Frage auf: Was passiert in den mit (1) und (2) markierten Zeilen? Hier sind meine konkreten Fragen:
- (1): Wählt der Compiler das Funktions-Template oder die Funktion aus und erweitert den
float
zudouble
? - (2): Sowohl die Funktion als auch das Funktions-Template sind ideale Kandidaten. Dies ist mehrdeutig. Verursachen diese Zeilen einen Compilerfehler?
Die Antworten auf diese Fragen sind intuitiv und folgen der allgemeinen Regel in C++. Der Compiler wählt den am besten passenden Kandidaten aus.
- (1): Das Funktions-Template ist ein besserer Kandidat, weil die Funktion eine Erweiterung von
float
nachdouble
erfordern würde. - (2): Das Funktions-Template und die Funktion sind ideale Kandidaten. In diesem Fall tritt eine zusätzliche Regel in Kraft. Wenn beide gleich gut passen, bevorzugt der Compiler die Funktion.
Wie zuvor hilft C++ Insights [1] dabei, diesen Prozess zu visualisieren.

Der Screenshot zeigt es explizit. Nur der Aufruf von max
für double
(2) löst die Instanziierungen des Funktions-Templates aus.
Beim weiteren Text gilt eine Einschränkung zu beachten: Ich ignoriere Concepts [2] in diesem Artikel.
Unterschiedliche Template Argumente
Nun verwende ich das Funktions-Template max
mit zwei Werten unterschiedlichen Datetyps.
template <Typname T>
T max(T lhs,T rhs) {
return (lhs > rhs)? lhs : rhs;
}
int main() {
max(10.5f, 5.5);
}
Dieses Programm möchte ich gerne mit C++ Insights [3] ausprobieren.

Wow! Was passiert hier? Warum wird der float
nicht zu einem double
erweitert? Ehrlich gesagt, der Compiler denkt anders:
- Der Compiler bestimmt die Template-Argumente mithilfe der Funktions-Argumente, falls dies möglich ist. In diesem Fall ist es möglich.
- Der Compiler führt diesen Prozess der Bestimmung der Template-Argumente für jedes Funktions-Argument durch.
- Für
10,5f
bestimmt der Compilerfloat
fürT,
für5,5
bestimmt der Compilerdouble
fürT
. - Natürlich kann
T
nicht gleichzeitigfloat
unddouble
sein. Wegen dieser Zweideutigkeit schlägt die Kompilierung fehl.
Zweite Einschränkung: Ich habe den Prozess der Ableitung von Template-Argumenten vereinfacht. Ich werde in einem zukünftigen Post über Template-Argument-Deduktion für Funktions- und Klassen-Templates schreiben.
Natürlich wollen wir Werte unterschiedlichen Typs vergleichen.
Zwei Typ-Parameter
Die Lösung scheint ganz einfach zu sein. Ich führe einfach einen zweiten Typ-Parameter ein.
template <Typname T, Typname T2>
max(T lhs,T2 rhs) {
return (lhs > rhs)? lhs : rhs;
}
int main() {
max(10.5f, 5.5);
}
Einfacher geht es nicht! Oder? Es gibt eine ernsthafte Herausforderung in diesem Beispiel. Man beachte die drei Fragezeichen als Rückgabetyp. Dieses Problem tritt typischerweise dann auf, wenn das Funktions-Template mehr als einen Typ-Parameter besitzt. Welchen Rückgabetyp benötigt das Funktions-Template? Soll in diesem konkreten Fall, sollte der Rückgabetyp T, T2
, oder ein von T
und T2
abgeleiteter Typ R
sein? Das war vor C++11 eine herausfordernde Aufgabe, aber mit C++11 ist es ziemlich einfach.
Hier sind ein paar Lösungen, die ich im Kopf habe:
// automaticReturnTypeDeduction.cpp
#include <type_traits>
template <typename T1, typename T2> // (1)
typename std::conditional<(sizeof(T1) > sizeof(T2)),
T1, T2>::type max1(T1 lhs,T2 rhs) {
return (lhs > rhs)? lhs : rhs;
}
template <typename T1, typename T2> // (2)
typename std::common_type<T1, T2>::type max2(T1 lhs,T2 rhs) {
return (lhs > rhs)? lhs : rhs;
}
template <typename T1, typename T2> // (3)
auto max3(T1 lhs,T2 rhs) {
return (lhs > rhs)? lhs : rhs;
}
int main() {
max1(10.5f, 5.5);
max2(10.5f, 5.5);
max3(10.5f, 5.5);
}
Die ersten beiden Versionen max1
(1) und max2
(2) basieren auf der type-traits Bibliothek. Die dritte Version max3
(3) nutzt die automatische Typableitung von auto
.
max1
(1):typename std::conditional<(sizeof(T1) > sizeof(T2)), T1, T2>::type
gibt den TypT1
oderT2
zurück, der größer ist. std::conditional [4] ist eine Art ternärer Operator zur Kompilierzeit.
max2
(2):typename td::common_type<T1, T2>::type
gibt den gemeinsamen Typ der TypenT1
undT2
zurück. std::common_type [5] kann eine beliebige Anzahl von Argumenten akzeptieren.max3
(3):auto
sollte selbsterklärend sein.
Der typename
vor dem Rückgabetyp der Funktions-Templates max1
und max2
mag irritieren: T1
und T2
sind abhängige Namen. Ein abhängiger Name ist ein Name, der von einem Template-Parameter abhängt. In diesem Fall müssen wir dem Compiler einen Hinweis geben, dass T1
und T2
Typen sind. T1
und T2
könnten auch Nicht-Typen oder Templates sein.
Dritte Einschränkung: Ich schreibe in einem weiteren Artikel über abhängige Typen.
Schauen wir uns an, was C++ Insights bietet. Ich zeige nur die Template-Instanziierungen. Wer das gesamte Programm analysieren willt, folge diesem Link: C++ Insights [6].
max1
(1): Hier lässt sich nur raten, welchen Rückgabetyp das Funktions-Template besitzt. In der Rückgabeanweisung wird der kleinere Typ (float
) zudouble
umgewandelt.

max2
(2): Wie beimax1
lässt die Rückgabeanweisung den Rückgabetyp erahnen: derfloat
-Wert wird zudouble
gewandelt.

max3
(3): Jetzt können wir den Rückgabetyp explizit sehen: Es ist eindouble
.

Wie geht es weiter?
In diesem Artikel habe ich die Herausforderung unterschiedlicher Typen der Funktions-Argumente gelöst, indem ich mehrere Typ-Parameter verwendet habe. In meinem nächsten Artikel wähle ich einen anderen Ansatz und gebe die Template-Argumente explizit an.
( [7])
URL dieses Artikels:
https://www.heise.de/-6041663
Links in diesem Artikel:
[1] https://cppinsights.io/lnk?code=dGVtcGxhdGUgPHR5cGVuYW1lIFQ+ClQgbWF4KFQgbGhzLFQgcmhzKSB7CiAgICByZXR1cm4gKGxocyA+IHJocyk%2FIGxocyA6IHJoczsKfQoKZG91YmxlIG1heChkb3VibGUgbGhzLCBkb3VibGUgcmhzKSB7CiAgICByZXR1cm4gKGxocyA+IHJocyk%2FIGxocyA6IHJoczsKfQoKaW50IG1haW4oKSB7CiAgCiAgICBtYXgoMTAuNWYsIDUuNWYpOwogICAgbWF4KDEwLjUsIDUuNSk7CiAgCn0KCg==&insightsOptions=cpp2a&std=cpp2a&rev=1.0
[2] https://www.grimm-jaud.de/index.php/blog/tag/concepts
[3] https://cppinsights.io/lnk?code=dGVtcGxhdGUgPHR5cGVuYW1lIFQ+ClQgbWF4KFQgbGhzLFQgcmhzKSB7CiAgICByZXR1cm4gKGxocyA+IHJocyk%2FIGxocyA6IHJoczsKfQoKaW50IG1haW4oKSB7CiAgCiAgICBtYXgoMTAuNWYsIDUuNSk7CiAgCn0KCg==&insightsOptions=cpp2a&std=cpp2a&rev=1.0
[4] https://en.cppreference.com/w/cpp/types/conditional
[5] https://en.cppreference.com/w/cpp/types/common_type
[6] https://cppinsights.io/lnk?code=Ly8gYXV0b21hdGljUmV0dXJuVHlwZURlZHVjdGlvbi5jcHAKCiNpbmNsdWRlIDx0eXBlX3RyYWl0cz4KCnRlbXBsYXRlIDx0eXBlbmFtZSBUMSwgdHlwZW5hbWUgVDI+ICAgICAgLy8gKDEpCnR5cGVuYW1lIHN0ZDo6Y29uZGl0aW9uYWw8KHNpemVvZihUMSkgPiBzaXplb2YoVDIpKSwgVDEsIFQyPjo6dHlwZSBtYXgxKFQxIGxocyxUMiByaHMpIHsKICAgIHJldHVybiAobGhzID4gcmhzKT8gbGhzIDogcmhzOwp9Cgp0ZW1wbGF0ZSA8dHlwZW5hbWUgVDEsIHR5cGVuYW1lIFQyPiAgICAgIC8vICgyKQp0eXBlbmFtZSBzdGQ6OmNvbW1vbl90eXBlPFQxLCBUMj46OnR5cGUgbWF4MihUMSBsaHMsVDIgcmhzKSB7CiAgICByZXR1cm4gKGxocyA+IHJocyk%2FIGxocyA6IHJoczsKfQoKdGVtcGxhdGUgPHR5cGVuYW1lIFQxLCB0eXBlbmFtZSBUMj4gICAgIC8vICgzKQphdXRvIG1heDMoVDEgbGhzLFQyIHJocykgewogICAgcmV0dXJuIChsaHMgPiByaHMpPyBsaHMgOiByaHM7Cn0KCmludCBtYWluKCkgewogIAogIAltYXgxKDEwLjVmLCA1LjUpOyAgICAgICAgICAgICAgICAgIAogICAgbWF4MigxMC41ZiwgNS41KTsgICAgICAgICAgICAgICAgICAKICAgIG1heDMoMTAuNWYsIDUuNSk7ICAgICAgICAgICAgICAgICAgCiAgCn0=&insightsOptions=cpp2a&std=cpp2a&rev=1.0
[7] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien