zurück zum Artikel

Funktions-Templates

Rainer Grimm

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

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

Jetzt möchte ich in die Details eintauchen. Was passiert, wenn ich Funktions-Templates und gewöhnliche Funktionen (kurz: Funktionen) verwende?

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:

Die Antworten auf diese Fragen sind intuitiv und folgen der allgemeinen Regel in C++. Der Compiler wählt den am besten passenden Kandidaten aus.

Wie zuvor hilft C++ Insights [1] dabei, diesen Prozess zu visualisieren.

Function-Templates

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.

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

Wow! Was passiert hier? Warum wird der float nicht zu einem double erweitert? Ehrlich gesagt, der Compiler denkt anders:

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.

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.

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].

Function-Templates
Function-Templates
Function-Templates

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