In der modernen Softwareentwicklung sind Schlüssel-Wert-Datenstrukturen unverzichtbar. Sie ermöglichen die Zuordnung von einzigartigen Schlüsseln zu zugehörigen Werten und finden sich in vielen Programmiersprachen wie Python mit Dictionaries, Java mit HashMaps oder JavaScript mit Objekten. C++ bildet hier keine Ausnahme und verfügt mit std::map und std::unordered_map über zwei Standardimplementierungen für solche Datenstrukturen. Mit dem Fortschritt des C++-Standards, insbesondere C++20, wurden deutliche Vereinfachungen und Erweiterungen eingeführt, die die Iteration über diese Strukturen nicht nur eleganter, sondern auch performanter gestalten können. Dieses Thema, die geschmeidige Durchquerung von Schlüsseln und Werten mithilfe der neuen Range-Views von C++20, soll im Folgenden umfassend beleuchtet werden.
Die Bedeutung von Schlüssel-Wert-Datenstrukturen in C++ liegt in ihrer Vielseitigkeit. std::map implementiert intern meist einen balancierten Suchbaum, etwa einen Red-Black-Baum. Dadurch sind Einfügungen, Löschungen und Suchen logarithmisch komplex, und die Schlüssel sind stets sortiert. Im Gegensatz dazu arbeitet std::unordered_map mit einer Hashtabelle, wodurch durchschnittlich konstante Zeitkomplexität bei Zugriffen entsteht, allerdings ohne eine definierte Reihenfolge der Schlüssel. Die Wahl zwischen diesen beiden hängt somit stark von den Anforderungen an die Datenorganisation und Zugriffszeiten ab.
Wird in praktischer Programmierung oft nur mit den Schlüsseln oder nur mit den Werten einer Map gearbeitet, war der Code in C++ vor C++20 häufig umständlich, da jeweils Iteratoren auf die Schlüssel-Wert-Paare genutzt werden mussten, und der Zugriff auf Schlüssel oder Werte manuell herausgeschrieben werden musste. C++20 revolutioniert dies durch das Ranges-Konzept und speziell die Views std::ranges::views::keys und std::ranges::views::values. Diese bieten eine elegante, expressive Möglichkeit, direkt und sauber über nur die Schlüssel oder Werte einer Map zu iterieren – ohne dabei auf die vollständigen Paare zugreifen zu müssen. Ein repräsentatives Beispiel sind zwei Funktionen: eine zur Summierung aller Werte in einer Map und eine andere zum Zählen, wie viele Schlüssel mit einem bestimmten Präfix beginnen. Durch diese Range-Views wird der Code nicht nur kürzer, sondern auch übersichtlicher und näher an einer funktionalen Programmierweise.
Funktionen wie std::accumulate und std::ranges::count_if kommen in Kombination mit diesen Views hervorragend zur Geltung und ermöglichen deklarative Programmiermuster. Ein typisches Summierungsbeispiel extrahiert die Werte mittels std::ranges::views::values und summiert sie mit std::accumulate, während ein Zähler für Schlüssel, die mit einem bestimmten String beginnen, mit std::ranges::views::keys und einem Lambda-Ausdruck realisiert wird, der die neue Methode starts_with verwendet. Gerade die Einführung von starts_with in den neueren C++-Standards trägt dazu bei, das Handling von String-Präfixen auf ein neues Level an Lesbarkeit und Effizienz zu bringen. Neben der funktionalen Programmierweise erlaubt C++20 aber auch weiterhin die klassische Schleifenstruktur, die viele Entwickler, darunter auch Fachleute wie Daniel Lemire, als besser lesbar empfinden. So lassen sich die Range-Views auch in einem traditionellen for-each Stil verwenden, indem die Werte oder Schlüssel einfach direkt in der Schleife durchlaufen werden.
Dieser pragmatische Ansatz verbindet Modernität mit Bewährtem und öffnet den Weg für verschiedene Teampräferenzen und Code-Stilrichtlinien. Für Entwickler, die noch auf C++11 oder älteren Standards arbeiten, stellt sich die Situation hingegen weniger komfortabel dar. Dort musste man entweder mit Iteratoren direkt arbeiten oder auf umfangreiche Loops zurückgreifen, wo Schlüssel und Werte explizit aus den Paaren extrahiert werden mussten. Hinzu kam, dass Funktionen wie starts_with noch nicht existierten, was zu umständlicheren und weniger lesbaren Lösungen mit String-Compare-Methoden führte. Ein weiterer interessanter Aspekt ist die messbare Leistungsverbesserung und der unterschiedliche Einfluss der CPU-Architektur und Compiler auf die Ausführungsgeschwindigkeit solcher Standard-Operationen.
Bei Benchmark-Tests mit großen Maps von tausend Schlüsseln zeigte sich, dass die Iteration über std::unordered_map oft schneller ist als über std::map. Dies liegt an der internen Hashtabelle und dem Wegfall der Baumstruktur. Darüber hinaus haben moderne Compiler und Prozessorarchitekturen wie etwa LLVM in Kombination mit Apple M2 oder GCC auf Intel-Prozessoren unterschiedlich starke Auswirkungen auf die Laufzeit und die Anzahl der ausgeführten Maschineninstruktionen. Besonders effizient erwies sich der Einsatz von C++20 Range-Views in Verbindung mit der starts_with-Funktion auf Apple M2-Systemen. Die Verarbeitungsrate pro Schlüssel war hier deutlich besser als auf Intel-Systemen, was auf die höhere Instructions-per-Cycle-Rate (IPC) der ARM-Architektur zurückgeführt wird.
Diese Erkenntnisse unterstreichen, dass moderne Sprachfeatures in Kombination mit der richtigen Plattform signifikante Performancegewinne bringen können. Neben der Performance gilt es auch den Wartbarkeitsaspekt zu betonen. Moderne C++-Features senken die Komplexität des Codes, reduzieren den Boilerplate-Anteil und fördern die Verwendung deklarativer Programmiermuster, die insgesamt zu weniger Fehlern und besserem Verständnis im Team führen. Entwickler können damit eleganter ausdrücken, was sie beabsichtigen, ohne sich in iteratorbasierte Syntax oder komplexe manuelle Schleifen zu verlieren. Die Kombination aus schlanker Syntax, klarer Semantik und leistungsfähiger Ausführung kann in vielen Projekten die Codequalität steigern und die Entwicklungszeit verkürzen.
Das macht C++20 gerade für professionelle Entwickler und Teams besonders attraktiv, die sowohl auf leistungsfähige Low-Level-Operationen als auch auf moderne Programmierparadigmen setzen möchten. Für Entwickler und Teams, die ihre Softwarebasis aktualisieren oder neu starten, lohnt sich daher die Auseinandersetzung mit den neuen Ranges in C++20 unbedingt. Die Beispiele zur Iteration von Schlüsseln und Werten in Maps sind nur ein kleiner Einblick in das Potenzial, das die Range-Bibliothek insgesamt bietet. Andere Range-Views ermöglichen weitere raffinierte Datenmanipulationen, Filterungen, Transformationen und Kompositionen, die wiederholtkehrende Programmieraufgaben stark vereinfachen können. Abschließend lässt sich festhalten, dass C++20 mit seinen Erweiterungen zur Iteration über Schlüssel-Wert-Datenstrukturen die Sprache deutlich attraktiver macht.