C++1y: Anonyme Funktionen 1.5

von Hubert Schmid vom 2013-05-19

Mit GCC‑4.8.1 und Clang‑3.3 wird es demnächst gleich zwei Implementierungen geben, die die Spracherweiterungen von C++11 vollständig unterstützen. Parallel zu dieser Entwicklung läuft die Spezfikation der nächsten Sprachversion, die als C++1y bekannt und bereits soweit vorangeschritten ist, dass mit ihrer Verabschiedung noch im kommenden Jahr zu rechnen ist. Dabei gibt es insbesondere im Bereich der anonymen Funktionen einige interessante Erweiterungen, auf die ich im Folgenden eingehe.

Typinferenz

Anonyme Funktionen bieten keine grundsätzlich neue Funktionalität. Vielmehr handelt es sich um syntaktischen Zucker, um die Entwicklungseffizienz durch bessere Lesbarkeitkeit und Wartbarkeit zu erhöhen. Ein typisches Beispiel für ihren Einsatz sind wiederkehrende Algorithmen, die ohne Unterstützung anonymer Funktionen häufig wie folgt aussehen.

std::unordered_map<int, std::string> items = ...; std::size_t count = 0; for (auto&& item : items) { if (item.second.length() == 2) { ++count; } }

Diese ausgeschriebenen Schleifen enthalten wenig Aussagekraft. Anonyme Funktionen in Kombination mit wiederverwendbaren Algorithmen versprechen dagegen Besserung, indem sie stärker auf das Ziel statt den Weg fokussieren. So lässt sich obige Schleife mit C++11 beispielsweise wie folgt schreiben.

auto count = std::count_if(items.begin(), items.end(), [](const std::pair<int, std::string>& item) { return item.second.length() == 2; });

Die Idee ist an diesem Beispiel zwar vermittelbar. Doch das Ergebnis finde ich nicht zufriedenstellend, da sich die Lesbarkeit durch die Änderung kaum verbessert. Im Vergleich zur ausgeschriebenen Schleife sind schlicht zu viele Symbole notwendig, so dass sich Vor- und Nachteile gegenseitig aufheben.

Mit C++1y wird das besser: Die Parametertypen anonymer Funktionen können dann durch auto ersetzt werden – vergleichbar zur Typinferenz lokaler Variablen. Das ist ein sinnvolles Feature, da die Parametertypen in den meisten Fällen für das Verständnis kaum hilfreiche Informationen beinhalten. Mit dieser Erweiterung vereinfacht sich obige Anweisung wie folgt:

auto count = std::count_if(items.begin(), items.end(), [](auto&& item) { return item.second.length() == 2; });

Das ist eine deutliche Verbesserung gegenüber der vorherigen Version. Die anonyme Funktion ist nun hinreichend kompakt, so dass die Vorteile gegenüber der ausgeschriebenen Schleife überwiegen. Allerdings zeigt einen Blick über den Tellerrand, dass es noch besser geht. In C# sieht der entsprechende Code beispielsweise so aus:

var count = items.Count(item => item.Value.Length == 2);

Templates

Besonders an der Typinferenz mit auto ist, dass die einzusetzenden Typen erst bei der Verwendung und nicht bereits bei der Definition bestimmt werden. So ist beispielsweise auch die folgende Zeile in C++1y zulässig.

auto&& compare_by_name = [](auto&& lhs, auto&& rhs) { return lhs.name < rhs.name; };

Der Ausdruck definiert ein Funktionstemplate, das erst bei Bedarf instantiiert wird. In diesem Fall wird es durch die Variable compare_by_name referenziert. Dadurch kann die selbe anonyme Funktion auch für unterschiedliche Typen verwendet werden. So werden im folgenden Beispiel sowohl Länder als auch Städte nach Namen sortiert.

auto countries = get_countries(); std::sort(countries.begin(), countries.end(), compare_by_name); for (auto&& country : countries) { auto cities = country.get_cities(); std::sort(cities.begin(), cities.end(), compare_by_name); }

Capture-Initializers

Anonyme Funktionen können einen Zustand besitzen, der sich meist auf Kopien oder Referenzen umgebender Objekte beschränkt. Mit C++1y kann dieser Zustand über die erweiterten Capture-Initializers flexibler eingesetzt werden. Ein großer Vorteil ist, dass sich Objekte nun auch mit std::move in anonyme Funktion verschieben lassen. Wie im folgenden Beispiel zu sehen, geht die Funktionalität allerdings weit darüber hinaus:

auto&& fibonacci = [a=0,b=1]() mutable { auto t = a; a = b; b += t; return t; }; std::generate_n(std::ostream_iterator<int>(std::cout, "\n"), 42, fibonacci);

Bei der anonymen Funktion handelt es sich um einen Generator, der die Fibonacci-Folge generiert. Dieser Generator kann für die Initialisierung von Reihungen verwendet werden, oder – so wie in der letzten Zeile zu sehen – einfach um die ersten 42 Fibonacci-Zahlen auszugeben.

Insgesamt bringt C++1y die Unterstützung anonymer Funktionen also einen deutlichen und wichtigen Schritt voran. Die Syntax ist zwar nicht so kompakt wie in C#. Doch dafür ist die Integration in die Sprache sehr gut gelungen.