Inhaltsverzeichnis

<ranges>

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.

views_pipelines.cpp
#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:

views_common.cpp
#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 << ' ';
}

Quellen und Senken

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.

to.cpp
#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; 
}

Fabriken

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 ³
istream_view.cpp
#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 << ' ';
}

Sichten

ü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 ³

Beispiele

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

views_examples.cpp
#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");
}