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.
![Function-Templates](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/0/3/2/1/1/templatesNew-8799506594e1993f.png)
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.
![Function-Templates](https://heise.cloudimg.io/width/395/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/0/3/2/1/1/functionTempateIntDouble-aa2d7ff5dc247a17.png)
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.
![Function-Templates](https://heise.cloudimg.io/width/457/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/0/3/2/1/1/functionTemplateFloatDoubleCircle-043b29f72a81da9b.png)
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.
![Function-Templates](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/0/3/2/1/1/functionTemplatesFloatDoubleError-9c9d7724546fdf9a.png)
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.
![Function-Templates](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/0/3/2/1/1/max1-1cfdee7586470115.png)
max2
(2): Wie beimax1
lässt die Rückgabeanweisung den Rückgabetyp erahnen: derfloat
-Wert wird zudouble
gewandelt.
![Function-Templates](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/0/3/2/1/1/max2-33a36d31d6b02d3b.png)
max3
(3): Jetzt können wir den Rückgabetyp explizit sehen: Es ist eindouble
.
![Function-Templates](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/1/0/3/2/1/1/max3-33e9e3ef6020f0a2.png)
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