Automatischer Rückgabetyp (C++11/14/20)
Nach der Vorstellung des automatischen Rückgabetyps in C++98, folgt nun die Umsetzung mit den Mitteln von C++11, C++14 und C++20.
Nach der Vorstellung des automatischen Rückgabetyps in C++98, folgt nun die Umsetzung mit den Mitteln von C++11, C++14 und C++20.
Zur Erinnerung: Hier ist die Aufgabe, die ich lösen möchte.
template <typename T, typename T2>
??? sum(T t, T2 t2) {
return t + t2;
}
Wer ein Funktions-Template mit mindestens zwei Parametern implementiert, kann im Allgemeinen nicht den Rückgabetyp der Funktion bestimmen. Natürlich sollte sum
den Typ zurückgeben, den die arithmetische Operation t + t2
ergibt.
std::cout << typeid(5.5 + 5.5).name(); // double
std::cout << typeid(5.5 + true).name(); // double
std::cout << typeid(true + 5.5).name(); // double
std::cout << typeid(true + false).name(); // int
Die ganze Geschichte findet sich in meinem vorherigen Artikel "Automatischen Rückgabetyp (C++98) [1]". Jetzt springe ich aber direkt zu C++11.
C++11
In C++11 gibt es im Wesentlichen zwei Möglichkeiten, dieses Problem zu lösen: Type-Traits oder auto
in Kombination mit decltype
.
Type-Traits
Die Type-Traits-Bibliothek besitzt die Funktion std::common_type [2]. Diese Funktion bestimmt zur Compilezeit den gemeinsamen Typ einer beliebigen Anzahl von Typen. Der gemeinsame Datentyp ist derjenige, in den alle Datentypen implizit konvertiert werden können. Wenn es ihn nicht gibt, spuckt der Compiler eine Fehlermeldung aus.
// automaticReturnTypeTypeTraits.cpp
#include <iostream>
#include <typeinfo>
#include <type_traits>
template <typename T, typename T2>
typename std::common_type<T, T2>::type sum(T t, T2 t2) {
return t + t2;
}
int main() {
std::cout << '\n';
std::cout << typeid(sum(5.5, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(5.5, true)).name() << '\n'; // double
std::cout << typeid(sum(true, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(true, false)).name() << '\n'; // bool
std::cout << '\n';
}
Der Einfachheit halber zeige ich die String-Darstellung des Datentyps im Quellcode an. Ich habe den MSVC-Compiler verwendet. Der GCC- oder Clang-Compiler würde einzelne Zeichen wie d
für double
und b
für bool
zurückgeben.
Es gibt einen feinen Unterschied zwischen std::common_type
und allen anderen Varianten, die ich im letzten Beitrag und in diesem Beitrag vorgestellt habe: std::common_type
gibt den gemeinsamen Datentyp zurück, meine Traits-Umsetzung des letzten Artikels hingegen und die auf auto
basierenden Variante in diesem Artikel, geben den Typ zurück, den der Ausdruck t + t2
ergibt.
auto
in Kombination mit decltype
auto
zum Bestimmen des Rückgabetyps einer Funktion in C++11 zu verwenden, ist viel zu umständlich.
Erstens muss man den sogenannten Trailing Return Type verwenden und zweitens den Rückgabetyp in einem decltype
-Ausdruck angeben.
// automaticReturnTypeTypeAutoDecltype.cpp
#include <iostream>
#include <typeinfo>
#include <type_traits>
template <typename T, typename T2>
auto sum(T t, T2 t2) -> decltype(t + t2) {
return t + t2;
}
int main() {
std::cout << '\n';
std::cout << typeid(sum(5.5, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(5.5, true)).name() << '\n'; // double
std::cout << typeid(sum(true, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(true, false)).name() << '\n'; // int
std::cout << '\n';
}
Der Ausdruck auto sum(T t, T2 t2) -> decltype(t + t2)
lässt sich folgendermaßen lesen: auto
drückt aus, dass der Rückgabetyp zu diesem Zeitpunkt noch nicht bekannt ist und er später angegeben wird. Diese Angabe erfolgt in dem Ausdruck decltype: decltype(t + t2)
. Der Rückgabetyp der Funktions-Template sum
ist der Typ, zu dem der arithmetische Ausdruck konvertiert. Was mir an dieser C++11-Syntax nicht gefällt, ist dass ich zweimal denselben Ausdruck t + t2
verwenden muss. Das ist fehleranfällig und überflüssig. Die Syntax für den Rrailing Return Type ist im Allgemeinen optional, aber für die automatische Ableitung des Rückgabetyps in C++11 und Lambdas erforderlich.
Schauen wir uns an, ob C++14 die Bestimmung des automatischen Rückgabetyps vereinfacht.
C++14
Mit C++14 haben wir eine bequeme Syntax für die automatische Bestimmung des Rückgabetyps ohne Redundanz.
// automaticReturnTypeTypeAuto.cpp
#include <iostream>
#include <typeinfo>
#include <type_traits>
template <typename T, typename T2>
auto sum(T t, T2 t2) {
return t + t2;
}
int main() {
std::cout << '\n';
std::cout << typeid(sum(5.5, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(5.5, true)).name() << '\n'; // double
std::cout << typeid(sum(true, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(true, false)).name() << '\n'; // int
std::cout << '\n';
}
In C++14 lässt sich einfach auto
als Rückgabetyp verwenden.
Machen wir den letzten Sprung zu C++20.
C++20
In C++20 lässt sich statt eines Unconstrained Placeholders einen Constrained Placeholder, auch bekannt als Concept, verwenden. Die Definition und Verwendung des Concepts Arithmetic
drückt explizit meine Absicht aus. In dem Funktions-Template sum
sind nur arithmetische Typen erlaubt.
// automaticReturnTypeTypeAuto.cpp
#include <iostream>
#include <typeinfo>
#include <type_traits>
template<typename T>
concept Arithmetic = std::is_arithmetic<T>::value;
Arithmetic auto sum(Arithmetic auto t, Arithmetic auto t2) {
return t + t2;
}
int main() {
std::cout << '\n';
std::cout << typeid(sum(5.5, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(5.5, true)).name() << '\n'; // double
std::cout << typeid(sum(true, 5.5)).name() << '\n'; // double
std::cout << typeid(sum(true, false)).name() << '\n'; // int
std::cout << '\n';
}
Ich definiere das Concept Arithmetic
, indem ich direkt die Type-Traits-Funktion std::is_arithmetic
einsetze. Die Funktion std::is_arithmetic
ist ein sogenanntes Compiletime Predicat. Ein Compiletime Predicat ist eine Funktion, die zur Compilezeit einen booleschen Wert zurückgibt.
Zu Concepts [3] habe ich bereits mehrere Artikel geschrieben, in denen sich die Details nachlesen lassen.
Wie geht's weiter?
Template-Metaprogrammierung oder die Programmierung zur Compilezeit mithilfe von Templates ist eine sehr leistungsfähige C++-Technik mit einem schlechten Ruf. Die Funktionen der Type-Traits-Bibliothek wie std::common_type
oder std::is_arithmetic
sind Beispiele für die Template-Metaprogrammierung in C++. In meinem nächsten Beitrag gehe ich näher auf die Template-Metaprogrammierung ein.
Online-Seminar: Clean Code: Best Practices für modernes C++
Ich freue mich darauf, mein nächstes Online-Seminar vom 14.12 - 16.12.2021 durchzuführen. Jeder Teilnehmer erhält eines meiner Bücher zur freien Auswahl und einen Gutschein für meinen Online Kurs C++ Fundamentals for Professionals [4].
Mehr Informationen zu dem Online-Seminar gibt es hier: Clean Code: Best Practices für modernes C++ [5] ( [6])
URL dieses Artikels:
https://www.heise.de/-6226085
Links in diesem Artikel:
[1] https://www.heise.de/blog/Automatischer-Rueckgabetyp-C-98-6219623.html
[2] https://en.cppreference.com/w/cpp/types/common_type
[3] https://www.grimm-jaud.de/index.php/blog/tag/concepts
[4] https://www.educative.io/courses/cpp-fundamentals-for-professionals
[5] https://www.modernescpp.de/index.php/c/2-c/34-clean-code-best-practices-fuer-modernes-c
[6] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien