Bereiche sind Elementfolgen, auf die begin(r)
und end(r)
anwendbar sind.
Die Algorithmen der Standardbibliothek erlauben seit C++20 die Schreibweise std::ranges::sort(v)
statt std::sort(begin(v), end(v))
.
Allerdings sind die einzelnen Algorithmen jeweils nacheinander auszuführen, also nicht koppelbar.
MIt einem anderen Ansatz soll dieser Nachteil behoben werden.
Im Namensraum std::ranges::views
oder kurz std::views
definierte Sichten auf Bereiche lassen sich über Pipes |
aneinander koppeln.
Sie liefern die Werte eines Bereiches einzeln auf Anforderung, also über lazy evaluation.
#include <iostream> #include <ranges> int main() { using namespace std::views; auto output = iota(1) | filter([](int x){ return x%2 == 0; }) | transform([](int x){ return 3*x; }) | take(10); // no calculation is done up to here (lazy evaluation) for (auto e : output) std::cout << e << ' '; }
Anfang und Ende eines Views müssen nicht denselben Typ besitzen. Werden Iteratoren des selben Typs benötigt, können diese mit std::views::common
erzeugt werden:
#include <iostream> #include <ranges> #include <vector> int main() { using namespace std::views; auto numbers = iota(1, 10) | common; auto v = std::vector<int>{numbers.begin(), numbers.end()}; for (auto e : v) std::cout << e << ' '; }
Sequentielle und assoziative (!) Container können als Quellbereiche dienen. Mit ranges::to<Container>()
(C++23) wird ein neuer Container als Senke für die verarbeiteten Daten erzeugt. Der Elementtyp wird automatisch bestimmt.
#include <iostream> #include <ranges> #include <set> #include <vector> int main() { std::set s{ 'h', 'a', 'l', 'o' }; auto v = s | std::views::take(2) | std::ranges::to<std::vector>(); for (auto e : v) std::cout << e; }
erzeugen Bereiche (° : definiert in C++20, ³ : C++23):
empty | leerer Bereich | ° |
single(value) | Bereich mit nur einem Wert | ° |
iota(start) | (unendlicher) Bereich mit Werten beginnend bei start , zählt mit ++ hoch | ° |
iota(start, end) | Zählbereich endet vor end | ° |
ranges::istream_view<T>(istream) | liest Werte vom Typ T aus dem Eingabestrom istream | ° |
repeat(value) | wiederholt den Wert unbegrenzt oft | ³ |
repeat(value, n) | liefert den Wert genau n Mal | ³ |
#include <iostream> #include <ranges> #include <sstream> #include <string> int main() { auto input = std::istringstream{"0 1 2 3 4 5 6 7 8 9"}; auto below_5 = std::ranges::istream_view<int>(input) | std::views::take_while([](auto x) { return x < 5; }); for (auto e : below_5) std::cout << e << ' '; }
übernehmen einen oder mehrere Bereiche, verarbeiten dessen / deren Elemente e
und geben sie weiter.
all | gibt alle Elemente weiter | ° |
adjacent<N> | erzeugt Tupel aus N benachbarten Elementen | ³ |
adjacent_transform<N>(f) | wendet f(e1,e2,…) auf N benachbarte Elemente e1,e2,… an | ³ |
as_const | Elemente dürfen nicht verändert werden | ³ |
as_rvalue | erspart Kopieren: Elemente können per "move" übernommen werden | ³ |
common | erzeugt Bereich, bei dem begin(r) und end(r) den selben Typ haben | ° |
cartesian_product(rg1,rg2,…) | bildet die Kreuzmenge, d.h. alle Tupel aus jedem mit jedem aus 2 oder mehr gegebenen Bereichen | ³ |
chunk(n) | unterteilt in Teilbereiche aus n aufeinander folgenden Elementen | ³ |
chunk_by(pred) | aufeinander folgende Elemente a ,b gehören zum gleichen Teilbereich, wenn pred(a,b) wahr ist | ³ |
counted(iter, n) | liefert n Werte, beginnend mit Iterator iter | ° |
drop(n) | überspringt die ersten n Elemente | ° |
drop_while(pred) | überspringt Elemente, solange pred(e) erfüllt ist | ° |
elements<N> | gibt den N -ten Bestandteil von tupelartigen Elementen weiter | ° |
enumerate | erzeugt Index-Wert-Paare | ³ |
filter(pred) | gibt nur die Elemente weiter, für die pred(e) erfüllt ist | ° |
join | wandelt einen Bereich von Bereichen in einen "flachen" Bereich um | ° |
join_with(pattern) | verbindet Teilbereiche und fügt pattern dazwischen ein | ³ |
keys | gibt alle Schlüssel von Schlüssel-Wert-Paaren weiter (elements<0> ) | ° |
pairwise | adjacent<2> | ³ |
pairwise_transform(f) | adjacent_transform<2>(f) | ³ |
reverse | kehrt Reihenfolge der Elemente um | ° |
slide(n) | erzeugt einen gleitenden Fensterbereich aus n Elementen | ³ |
stride(n) | springt immer n Elemente weiter | ³ |
take(n) | gibt nur die ersten n Elemente weiter | ° |
take_while(pred) | gibt Elemente nur solange weiter, wie pred(e) erfüllt ist | ° |
transform(f) | wendet f auf jedes Element e an, gibt f(e) weiter | ° |
split(delim) | erzeugt einen Bereich von Teilbereichen, delim kann einzelnes Element oder eine Folge sein | ° |
values | gibt alle Werte von Schlüssel-Wert-Paaren weiter (elements<1> ) | ° |
zip(rg1,rg2,…) | erzeugt Tupel aus zwei oder mehr Bereichen | ³ |
zip_transform(f,rg1,rg2,…) | ruft f(e1,e2,…) mit Argumenten e1,e2,… aus gegebenen Bereichen auf | ³ |
empty<T> : [] single(42) : [42] iota(0,6) : [0, 1, 2, 3, 4, 5] iota(0) | take(3) : [0, 1, 2] repeat(6) | take(3) : [6, 6, 6] counted(begin(v),size(v)) : [1, 3, 5, 4, 2] v | all : [1, 3, 5, 4, 2] v | drop_while(below_5) : [5, 4, 2] v | enumerate : [(0, 1), (1, 3), (2, 5), (3, 4), (4, 2)] v | filter(below_5) : [1, 3, 4, 2] v | reverse : [2, 4, 5, 3, 1] v | take_while(below_5) : [1, 3] v | transform(square) : [1, 9, 25, 16, 4] v | chunk(3) : [[1, 3, 5], [4, 2]] v | chunk_by(std::ranges::less{}) : [[1, 3, 5], [4], [2]] v | pairwise_transform(double_digit) : [13, 35, 54, 42] v | pairwise : [(1, 3), (3, 5), (5, 4), (4, 2)] v | slide(2) : [[1, 3], [3, 5], [5, 4], [4, 2]] v | stride(2) : [1, 5, 2] s : ['A', 'B', ' ', 'C', 'D', 'E'] s | split(' ') : [['A', 'B'], ['C', 'D', 'E']] s | split(' ') | join : ['A', 'B', 'C', 'D', 'E'] s | split(' ') | join_with("<>"s) : ['A', 'B', '<', '>', 'C', 'D', 'E'] zip(v, s) : [(1, 'A'), (3, 'B'), (5, ' '), (4, 'C'), (2, 'D')] zip_transform(double_digit, v, v) : [11, 33, 55, 44, 22] cartesian_product(v|take(2), s|take(2)) : [(1, 'A'), (1, 'B'), (3, 'A'), (3, 'B')] m : {"answer": 42, "wrong": 54} m | keys : ["answer", "wrong"] m | values : [42, 54]
ist die Ausgabe des Programms
#include <ranges> #include <map> #include <string> #include <vector> // waiting for C++23 support: P2286 Formatting Ranges ... #if defined(__cpp_lib_format_ranges) && defined(__cpp_lib_print) #include <print> template <typename Range, typename Msg> void show(Range&& r, Msg message) { std::print("{:<40}: {}", message, r); } template <typename Range, typename Msg> void show_range_of_ranges(Range&& r, Msg message) { show(r, message); } template <typename Range, typename Msg> void show_range_of_pairs(Range&& r, Msg message) { show(r, message); } template <typename Range, typename Msg> void show_map(Range&& r, Msg message) { show(r, message); } #else #include <iostream> auto decorate(auto e) { return e; } auto decorate(char e) { return std::string("\'") + e + "\'"; } auto decorate(std::string e) { return std::string("\"") + e + "\""; } template <typename Range, typename Msg> void show(Range&& r, Msg message) { std::cout.width(40); std::cout << std::left << message << " : ["; int cnt = 0; for (auto&& e : r) { std::cout << (cnt++ ? ", ":"") << decorate(e); } std::cout << "]\n"; } template <typename Range, typename Msg> void show_range_of_ranges(Range&& r, Msg message) { std::cout.width(40); std::cout << std::left << message << " : ["; int cnt = 0; for (auto&& inner_range : r) { std::cout << (cnt++ ? ", ":"") << "["; int inner_cnt = 0; for (auto&& e : inner_range) { std::cout << (inner_cnt++ ? ", ":"") << decorate(e); } std::cout << "]"; } std::cout << "]\n"; } template <typename Range, typename Msg> void show_range_of_pairs(Range&& r, Msg message) { std::cout.width(40); std::cout << std::left << message << " : ["; int cnt = 0; for (auto&& [a,b] : r) { std::cout << (cnt++ ? ", ":"") << "(" << decorate(a) << ", " << decorate(b) << ")"; } std::cout << "]\n"; } template <typename Range, typename Msg> void show_map(Range&& r, Msg message) { std::cout.width(40); std::cout << std::left << message << " : {"; int cnt = 0; for (auto&& [k,v] : r) { std::cout << (cnt++ ? ", ":"") << decorate(k) << ": " << decorate(v); } std::cout << "}\n"; } #endif // defined(__cpp_lib_format_ranges) && defined(__cpp_lib_print) int main() { using namespace std::views; using namespace std::string_literals; auto v = std::vector{ 1, 3, 5, 4, 2 }; auto s = "AB CDE"s; auto m = std::map<std::string,int>{{"wrong", 6*9}, {"answer", 42}}; auto below_5 = [](int x){ return x < 5; }; auto double_digit = [](int a, int b) { return 10*a + b; }; show( empty<int>, "empty<T>"); show( single(42), "single(42)"); show( iota(0,6), "iota(0,6)"); show( iota(0) | take(3), "iota(0) | take(3)"); show( repeat(6) | take(3), "repeat(6) | take(3)"); std::cout << '\n'; show( counted(begin(v), size(v)), "counted(begin(v),size(v))"); show( v | all, "v | all"); show( v | drop_while(below_5), "v | drop_while(below_5)"); show_range_of_pairs(v | enumerate, "v | enumerate"); show( v | filter(below_5), "v | filter(below_5)"); show( v | reverse, "v | reverse"); show( v | take_while(below_5), "v | take_while(below_5)"); show( v | transform([](int x){ return x*x; }), "v | transform(square)"); std::cout << '\n'; show_range_of_ranges( v | chunk(3), "v | chunk(3)" ); show_range_of_ranges( v | chunk_by(std::ranges::less{}), "v | chunk_by(std::ranges::less{})" ); show( v | pairwise_transform(double_digit), "v | pairwise_transform(double_digit)" ); show_range_of_pairs( v | pairwise, "v | pairwise" ); show_range_of_ranges( v | slide(2), "v | slide(2)" ); show( v | stride(2), "v | stride(2)" ); std::cout << '\n'; show( s , "s"); show_range_of_ranges( s | split(' '), "s | split(\' \')"); show( s | split(' ') | join, "s | split(\' \') | join"); show( s | split(' ') | join_with("<>"s), "s | split(\' \') | join_with(\"<>\"s)"); std::cout << '\n'; show_range_of_pairs( zip(v, s), "zip(v, s)" ); show( zip_transform(double_digit, v, v), "zip_transform(double_digit, v, v)" ); show_range_of_pairs( cartesian_product(v|take(2), s|take(2)), "cartesian_product(v|take(2), s|take(2))" ); std::cout << '\n'; show_map( m , "m"); show( m | keys , "m | keys"); show( m | values , "m | values"); }