Automatischer Rückgabetyp (C++11/14/20)

Modernes C++ Rainer Grimm  –  8 Kommentare

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.

Automatischer Rückgabetype (C++11/14/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)". 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. 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 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.

Mehr Informationen zu dem Online-Seminar gibt es hier: Clean Code: Best Practices für modernes C++