3 Gründe für Trailing-Return-Types in C++
Seit 2011 gibt es in C++ eine alternative Syntax für Funktionen, die unter dem Begriff Trailing-Return-Type
bekannt ist. Dabei steht der Rückgabetyp hinter der Parameterliste statt wie ursprünglich vor dem Funktionsnamen. Die neue und alte Syntax sind zueinander kompatibel und können im Code gemeinsam verwendet werden. Allerdings gibt es drei gute Gründe, warum ich die Verwendung der neuen Form empfehle, auch wenn sie zunächst ein wenig ungewohnt ist.
// old syntax
std::string to_string(int value);
// new syntax with trailing return type
auto to_string(int value) -> std::string;
Funktionsnamen sind wichtig
Der Funktionsname beschreibt kurz und prägnant die Funktionalität, und ist damit in den meisten Fällen der wichtigste Bestandteil, um eine Funktion zu finden und zu verstehen. Daher ist es wichtig, dass Funktionsnamen einfach und schnell für den Leser erfassbar sind. Abgesehen von visueller Unterstützung durch die Entwicklungsumgebung ist die einfachste Lösung, die Funktionsnamen abgesetzt an den Zeilenanfang zu stellen.
Dieses Vorgehen ist überzeugend für Funktionen mit komplexen Rückgabetypen, die im Zusammenhang mit Containern häufig auftreten. Das folgende Code-Fragment verdeutlicht den Unterschied für eine Funktion aus der Standardbibliothek.
// old syntax
std::pair<ForwardIterator, ForwardIterator> equal_range(
ForwardIterator first, ForwardIterator last, const Type& value);
// new syntax with trailing return type
auto equal_range(ForwardIterator first, ForwardIterator last, const Type& value)
-> std::pair<ForwardIterator, ForwardIterator>;
Die Trailing-Return-Types
machen allerdings auch ohne komplexe Datentypen einen großen Unterschied bei der Lesbarkeit. Im folgenden Beispiel sind zweimal zwei einfache Member-Funktionen zu sehen. Daran ist gut zu erkennen, dass die ausgerichteten Funktionsnamen im zweiten Fall wesentlich einfacher zu erfassen sind.
// member functions with old syntax
bool empty() const;
std::size_t size() const;
// member functions with new syntax and trailing return types
auto empty() const -> bool;
auto size() const -> std::size_t;
Zusammengefasst: Durch die neue Syntax sind Funktionsnamen einfacher erfassbar, wodurch sich die einfache Lesbarkeit und das schnelle Verständnis verbessert.
Namensräume
C++ hat die Syntax für Funktionen sowie die Trennung zwischen Deklaration und Definition von C geerbt. Spezifisch für C++ ist hingegen die Unterstützung für Namensräume, die sich mit der klassischen Funktionssyntax nur unzureichend verträgt. Das folgende Code-Fragment zeigt das Problem anhand einer vereinfachten Definition der Member-Funktion std::map::at
.
// old syntax - does not work because "mapped_type" can not be resolved
mapped_type& std::map::at(const key_type& key) { return ...; }
Diese Definition ist ungültig, weil der Rückgabetyp mapped_type
nicht aufgelöst werden kann, sofern die Funktion im globalen Namensraum definiert wird. Bei der alten Syntax müssen Rückgabetypen – im Gegensatz zu Parametertypen – qualifiziert werden. Korrekt muss es also std::map::mapped_type
heißen.
Mit der neuen Syntax ist das einfacher und intuitiver: Die Auflösung der Rückgabetypen funktioniert erstens genauso wie bei der Funktionsdeklaration innerhalb der Klassendefinition und zweitens genauso wie für die Parametertypen.
// new syntax - "mapped_type" is found in std::map
auto std::map::at(const key_type& key) -> mapped_type& { return ...; }
Optionale Rückgabetypen
Mit der kommenden Sprachversion C++14 ist die Angabe der Rückgabetypen in vielen Fällen optional – vorausgesetzt man verwendet die neue Funktionssyntax. Diese Erweiterung ist sehr nützlich, da in hinreichend vielen Fällen die explizite Wiederholung des Typs eher störend als hilfreich ist. Dazu zwei einfache Beispiele:
template <typename Type, typename ...Args>
auto make_unique(Args&&... args) // no return type
{
return std::unique_ptr<Type>(new Type(std::forward<Args>(args)...));
}
Auch ohne explizite Angabe des Rückgabetyps ist klar, was diese vereinfachte Variante der Funktion std::make_unique
zurückgibt. Denn schließlich steht der Typ gut sichtbar im Funktionsrumpf. Ähnlich verhält es sich im folgenden Fall. Die beiden Member-Funktionen sind zu einfach für eine explizite Typangabe.
class person
{
int _id;
std::string _last_name;
std::string _first_name;
public:
auto get_id() const { return _id; }
auto get_display_name() const { return _last_name + ", " + _first_name; }
};
Damit man also in Zukunft nicht inkonsistent bei der Wahl der Funktionssyntax ist, sollte man sich rechtzeitig damit auseinandersetzen und daran gewöhnen. Aufgrund der erheblichen Vorteile wage ich sogar die Voraussage, dass sich die neue Syntax in den nächsten Jahren in modernem C++ durchsetzen wird.