C++20: Die Ranges-Bibliothek
Dank der Ranges-Bibliothek in C++20 wird der Umgang mit der Standard Template Library deutlich angenehmer und mächtiger. Ihre Algorithmen sind lazy, agieren direkt auf den Containern und können verknüpft werden. Um es kurz zu machen: Die Bequemlichkeit und Mächtigkeit der Ranges-Bibliothek beruht auf ihren funktionalen Ideen.
Dank der Ranges-Bibliothek in C++20 wird der Umgang mit der Standard Template Library deutlich angenehmer und mächtiger. Ihre Algorithmen sind lazy, agieren direkt auf den Containern und können verknüpft werden. Um es kurz zu machen: Die Bequemlichkeit und Mächtigkeit der Ranges-Bibliothek beruht auf ihren funktionalen Ideen.
Bevor ich auf die Details eingehe, möchte ich ein erstes Beispiel zur Ranges-Bibliothek vorstellen:
// rangesFilterTransform.cpp
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
auto results = numbers | std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * 2; });
for (auto v: results) std::cout << v << " "; // 4 8 12
}
Du musst den Ausdruck von links nach rechts lesen. Das Pipe-Symbol steht für die Verknüpfung von Funktionen: Zuerst werden alle Elemente akzeptiert, die gerade sind (std::views::filter([](int n){ return n % 2 == 0; })
). Danach wird jedes verbleibende Element verdoppelt (std::views::transform([](int n){ return n * 2; }
)). Dieses kleine Beispiel zeigt bereits zwei neue Feature der Ranges-Bibliothek: zum einen die Funktionskomposition, und zum anderen agiert diese Funktionskomposition direkt auf dem Container.
Jetzt solltest du für die Details gewappnet sein. Damit sind wir wieder am Startpunkt angelangt: Ranges und Views sind Concepts.

Range
std::range
: Ein Range ist eine Menge von Elementen, über die iteriert werden kann. Dieser Range verfügt über einen Begin-Iterator und ein Sentinel (Abschlusselement). Selbstverständlich sind die Container der Standard Template Library (STL) Ranges.
Es gibt einige Verfeinerungen von std::range
:
std::ranges::input_range
: steht für einen Range, dessen Iteratoren einem Input-Iterator genügen (damit lässt sich zumindest einmal über den Range iterieren)std::ranges::output_range
: steht für einen Range, dessen Iteratoren einem Output-Iterator genügenstd::ranges::forward_range
: steht für einen Range, dessen Iteratoren einem Forward-Iterator genügen (damit lässt sich mehr als einmal über den Range iterieren)std::ranges::bidirectional_range:
steht für einen Range, dessen Iteratoren einem Birdectional-Iterator genügen (damit lässt sich mehr als einmal über den Range vorwärts und rückwärts iterieren)std::ranges::random_access_range:
steht für einen Range, dessen Iteratoren einem Random-Access-Iterator genügen (damit lässt sich in konstanter Zeit auf ein beliebiges Element des Ranges mithilfe des Indexoperators [] zugreifen)std::ranges::contiguous_range:
steht für einen Range, dessen Iteratoren einem Contiguous-Iterator genügen (die Elemente des Ranges sind kontinuierlich im Speicher angeordnet)

Die Container der STL und der std::string
setzen verschiedene Concepts um: Ein Containter, der das Concept std::ranges::contiguous_range
unterstützt, unterstützt auch alle vorherigen Concepts in der Tabelle wie std::ranges::random_access_range
, std::ranges::bidirectional_range
und std::ranges::input_range
. Diese Beobachtung gilt natürlich auch für die anderen Ranges der Tabelle.
View
- Ein View lässt sich auf einen Range anwenden. Dabei wird eine Operation ausgeführt. Views besitzen keine Daten. Konsequenterweise sind seine Copy-, Move- oder Zuweisungsoperationen konstant. Eric Niebler, Autor der ranges-v3 [1]-Implementierung, die Grundlage für die Ranges-Bibliothek in C++20 ist, beschreibt Ranges mit folgenden Worten: "Views are composable adaptations of ranges where the adaptation happens lazily as the view is iterated."
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
auto results = numbers | std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * 2; });
In dem Codebeispiel ist numbers
die Range, während std::views::filter
und std::views::transform
für die Views stehen.
Dank der Mächtigkeit der Views erlaubt die Ranges-Bibliothek das Programmieren im funktionalen Stil. Views lassen sich verknüpfen und sind lazy. Ich habe bereits zwei Views vorgestellt. Natürlich enthält C++20 deutlich mehr:
std::all_view, std::views::all // takes all elements
std::ref_view // takes all elements of another view
std::filter_view, std::views::filter // takes the elements which satisfies the predicate
std::transform_view, std::views::transform // transforms each element
std::take_view, std::views::take // takes the first N elements of another view
std::take_while_view, std::views::take_while // takes the elements of another view as long as the predicate returns true
std::drop_view, std::views::drop // skips the first N elements of another view
std::drop_while_view, std::views::drop_while // skips the initial elements of another view until the predicate returns false
std::join_view, std::views::join // joins a view of ranges
std::split_view, std::views::split // splits a view by using a delimiter
std::common_view, std::views::common // converts a view into a std::common_range
std::reverse_view, std::views::reverse // iterates in reverse order
std::basic_istream_view, std::istream_view // applies operator>> on the view
std::elements_view, std::views::elements // creates a view on the N-th element of tuples
std::keys_view, std::views::keys // creates a view on the first element of a pair-like values
std::values_view, std::views::values // creates a view on the second elements of a pair-like values
Im Allgemeinen lässt sich ein View wie std::views::transform
mit dem alternativen Namen std::transform_view
verwenden. Bei meiner weiteren Vorstellung der Ranges-Bibliothek werde ich die Views anwenden.
Implementierungsstatus
Soweit ich weiß, gibt es zum jetzigen Zeitpunkt (Februar 2020) keine Implementierung der Ranges-Bibliothek. Das ist aber kein Problem, da die bereits erwähnte ranges-v3 [2]-Implementierung verfügbar ist. Am einfachsten ist es, den Online-Compiler Wandbox [3] oder den Compiler Explorer [4] mit dem HEAD GCC zu verwenden. Dazu müssen meine Beispiele wie rangesFilterExample.cpp
leicht modifiziert werden:
- Ersetze den Namensraum
std::views
mitranges::views.
- Ersetze die Headerdatei
<ranges>
mit<ranges/v3/all.hpp>
. Mehr Details dazu gibt es in der ranges-v3 [5]-Implementierung. - Übersetze das Programm mit C++20-Unterstützung:
-std=c++2a.
- Wenn du den Compiler Explorer verwendest, solltest du die trunk-Version der ranges-v3 [6]-Implementierung einsetzen. Der folgende Screenshot sollte helfen, die notwendige Option zu finden:

Wende ich die Transformationsschritte auf das Programm rangesFilterTransform.cpp
an, erhalte ich das folgende Programm:
// rangesV3FilterTransform.cpp
#include <iostream>
#include <range/v3/all.hpp>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
auto results = numbers | ranges::views::filter([](int n){ return n % 2 == 0; })
| ranges::views::transform([](int n){ return n * 2; });
for (auto v: results) std::cout << v << " ";
}
Dank der Wandbox [7] muss ich dieses Mal die Ausgabe nicht vortäuschen.

Wie geht's weiter?
In diesem Artikel ging ich auf die Grundlagen zur Ranges-Bibliothek ein. Dank ihnen kann ich mich in meinem nächsten Artikel auf die Mächtigkeit der Ranges fokussieren. Die Ranges-Bibliothek erweitert C++20 mit zwei neuen Konzepten: Funktionskomposition und Lazy Evaluation. Diese Erweiterung ist genau der Grund dafür, dass die Ranges-Bibliothek zu den großen Vier in C++20 [8] gezählt werden muss. Jede ihrer Komponenten ändert die Art und Weise, wie wir C++ verwenden werden. ( [9])
URL dieses Artikels:
https://www.heise.de/-4661566
Links in diesem Artikel:
[1] https://github.com/ericniebler/range-v3
[2] https://github.com/ericniebler/range-v3
[3] https://wandbox.org/
[4] https://godbolt.org/
[5] https://github.com/ericniebler/range-v3
[6] https://github.com/ericniebler/range-v3
[7] https://wandbox.org/
[8] https://heise.de/-4568956
[9] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien