Beginner's Guide
    First Steps
    Input & Output
    Custom Types – Part 1
    Diagnostics
    Standard Library – Part 1
    Function Objects
    Standard Library – Part 2
    Code Organization
    Custom Types – Part 2
    Generic Programming
    Memory Management
    Software Design Basics

    Standard Library Composable Range Views C++20 Composable Views C++20 Views

    Note that the C++23 examples require the latest compilers (e.g., g++13) to run and some utilities like ranges::to or views::enumerate might not be available yet.

    Intro

    <ranges> Library

    Factories that return a view object

    std::views::NAME (args…) → VIEW-OBJECT

    • called like a function (usually implemented as function objects)
    • the preferred way of creating views, but not available for all view types
    #include <ranges>
    std::vector<int> v {7,2,6,3,4};
    for (int x : std::views::reverse(v)) {   cout << x <<' '; } // 4 3 6 2 7
    // using pipeline notation:
    for (int x : v | std::views::reverse) {   cout << x <<' '; } // 4 3 6 2 7
    // storing a view in a variable:
    auto rv = std::views::reverse(v);
    // 'rv' has type 'std::ranges::reverse_view'
    for (int x : rv) {   cout << x <<' '; } // 4 3 6 2 7
    

    View Types

    std::ranges::NAME_view {args…}

    #include <ranges>
    std::vector<int> v {7,2,6,3,4};
    std::ranges::reverse_view rv {v};
    for (int x : rv) {   cout << x <<' '; } // 4 3 6 2 7
    

    Composing Views

    Views that take a single input range (and possibly some other non-range parameters) can be chained with the pipe | operator:

    view1 | view2 | view3 |

    Example 1

    standard library range view composition example
    #include <ranges>
    std::vector<int> input {1,3,4,2,5,2,7,8};
    auto const is_even = [] (int x) { return !(x & 1); };
    auto result = input
      | std::views::reverse
      | std::views::filter(is_even);
    for (int x : result) {   cout << x <<' '; } // 8 2 2 4
    

    Example 2

    standard library range view composition example
    #include <ranges>
    namespace views = std::views; // alias
    std::vector<int> input {1,3,2,2,0,1,0,8,5,0};
    auto const greater_0 = [] (int x) { return x > 0; };
    auto const plus_1 = [] (int x) { return x + 1; };
    auto result = input
      | views::drop(2)
      | views::filter(greater_0)
      | views::transform(plus_1);
    for (int x : result) {   cout << x <<' '; } // 3 3 2 9 6
    

    Example 3

    standard library range view composition example
    #include <ranges>
    namespace views = std::views; // alias
    std::vector<int> input {8,7,3,3,9};
    auto result = views::zip(input, views::iota(1))
      | views::reverse;
    fmt::print("{}\n", result);
    // [(9,5), (3,4), (3,3), (7,2), (8,1)]
    

    Conversion To Containers

    C++23
    standard library range view 'ranges_to' visual example

    std::ranges::to might not be available yet in the latest g++/libstdc++

    Example 1

    standard library range view composition example
    #include <ranges>
    namespace views = std::views; // alias
    std::vector<char> input {'a','b','c','d','e'};
    auto result = input
      | views::slide(2)
      | views::join
      | std::ranges::to<std::string>();
    std::cout << result << '\n';
    // abbccdde
    

    Example 2

    standard library range view composition example
    #include <ranges>
    namespace views = std::views; // alias
    std::vector<int> input {9,8,7,6};
    auto result = views::zip(input, views::iota(1))
      | views::reverse
      | std::ranges::to<std::list>();
    fmt::print("{}\n", result);
    // [(6,4), (7,3), (8,2), (9,1)]
    

    Element Selectors

    filter filter C++20

    #include <ranges>
    std::vector<int> v {7,2,6,3,4};
    // custom predicate:
    auto const is_even = [] (int x) { return !(x & 1); };
    
    fmt::print("{}\n", std::views::filter(v,is_even)); // [2,6,4]
    // using pipeline notation: fmt::print("{}\n", v | std::views::filter(is_even)); // [2,6,4]
    // using an explicit view object: std::ranges::filter_view fv {v,is_even}; fmt::print("{}\n", fv); // [2,6,4]

    stride stride C++23

    #include <ranges>
    std::vector<int> v {0,1,2,3,4,5,6,7,8};
    
    fmt::print("{}\n", std::views::stride(v,2)); // [0,2,4,6,8] fmt::print("{}\n", std::views::stride(v,3)); // [0,3,6]
    // using pipeline notation: fmt::print("{}\n", v | std::views::stride(2)); // [0,2,4,6,8]
    // using an explicit view object: std::ranges::stride_view sv2 {v,2}; fmt::print("{}\n", sv2); // [0,2,4,6,8]

    Subranges Selectors

    counted counted C++20

    #include <ranges>
    std::vector<int> v {9,1,3,7,1,2,5,8};
    
    fmt::print("{}\n", std::views::counted(v.begin()+2,4)); // [3,7,1,2]

    Note that std::views::counted is not pipelinable and that there's also no std::ranges::counted_view type.

    take take C++20

    #include <ranges>
    std::vector<int> v {0,1,2,3,4};
    
    fmt::print("{}\n", std::views::take(v,3)); // [0,1,2]
    // using pipeline notation: fmt::print("{}\n", v | std::views::take(3)); // [0,1,2]
    // using an explicit view object: std::ranges::take_view tv {v,3}; fmt::print("{}\n", tv); // [0,1,2]

    drop drop C++20

    #include <ranges>
    std::vector<int> v {0,1,2,3,4};
    
    fmt::print("{}\n", std::views::drop(v,2)); // [2,3,4]
    // using pipeline notation: fmt::print("{}\n", v | std::views::drop(2)); // [2,3,4]
    // using an explicit view object: std::ranges::drop_view dv {v,2}; fmt::print("{}\n", dv); // [2,3,4]

    take_while take_while C++20

    #include <ranges>
    std::vector<int> v {2,6,4,3,8};
    // custom predicate:
    auto const is_even = [] (int x) { return !(x & 1); };
    
    fmt::print("{}\n", std::views::take_while(v,is_even)); // [2,6,4]
    // using pipeline notation: fmt::print("{}\n", v | std::views::take_while(is_even)); // [2,6,4]
    // using an explicit view object: std::ranges::take_while_view tv {v,is_even}; fmt::print("{}\n", tv); // [2,6,4]

    drop_while drop_while C++20

    #include <ranges>
    std::vector<int> v {2,6,7,6,5};
    // custom predicate:
    auto const is_even = [] (int x) { return !(x & 1); };
    
    fmt::print("{}\n", std::views::drop_while(v,is_even)); // [7,6,5]
    // using pipeline notation: fmt::print("{}\n", v | std::views::drop_while(is_even)); // [7,6,5]
    // using an explicit view object: std::ranges::drop_while_view dv {v,is_even}; fmt::print("{}\n", dv); // [7,6,5]

    Sliding Window Views

    slide slide C++23

    #include <ranges>
    std::vector<int> v {0,1,2,3,4};
    
    for (auto x : std::views::slide(v,2)) { fmt::print("{} ",x); } // [0,1] [1,2] [2,3] [3,4]
    for (auto x : std::views::slide(v,3)) { fmt::print("{} ",x); } // [0,1,2] [1,2,3] [2,3,4]
    // using pipeline notation: for (auto x : v | std::views::slide(3)) { fmt::print("{} ",x); } // [0,1,2] [1,2,3] [2,3,4]
    // using an explicit view object: std::ranges::slide_view sv3 {v,3}; for (auto x : sv3) { fmt::print("{} ",x); } // [0,1,2] [1,2,3] [2,3,4]

    adjacent adjacent C++23

    #include <ranges>
    std::string s = "abcde";
    
    for (auto p : std::views::adjacent<3>(s)) { fmt::print("{} ",p); } // abc bcd cde
    // using pipeline notation: for (auto p : s | std::views::adjacent<3>) { fmt::print("{} ",p); } // abc bcd cde for (auto p : s | std::views::pairwise) { fmt::print("{} ",p); } // ab bc cd de

    Function Application

    transform transform C++20

    #include <ranges>
    #include <cctype>  // std::toupper
    std::string s = "abcd";
    // function to be applied:
    auto const to_upper = [] (char c) { 
        return static_cast<char>(std::toupper(static_cast<unsigned char>(c))); 
    };
    
    fmt::print("{}\n", std::views::transform(s,to_upper)); // [A,B,C,D]
    // using pipeline notation: fmt::print("{}\n", s | std::views::transform(to_upper)); // [A,B,C,D]
    // using an explicit view object: std::ranges::transform_view fv {s,to_upper}; fmt::print("{}\n", fv); // [A,B,C,D]

    adjacent_transform adjacent_transform C++23

    #include <ranges>
    std::string s = "abcde";
    auto const f3 = [] (char x, char y, char z) {   return std::string{'<',x,y,z,'>'}; };
    for (auto p : std::views::adjacent_transform<3>(s,f3)) {   fmt::print("{} ",p); }
    // <abc> <bcd> <cde>
    
    auto const f2 = [] (char x, char y) { return std::string{'<',x,y,'>'}; }; for (auto p : std::views::pairwise_transform(s,f2)) { fmt::print("{} ",p); } // <ab> <bc> <cd> <de>
    // using pipeline notation: for (auto p : s | std::views::pairwise_transform(f2)) { fmt::print("{} ",p); } // <ab> <bc> <cd> <de>

    Tokenization Views

    split split C++20

    #include <ranges>
    std::vector<int> v {2,6,1,0,9,5,3,1,0,7};
    
    for (auto x : std::views::split(v,1)) { fmt::print("{} ",x); } // [2,6] [0,9,5,3] [0,7]
    std::array<int,2> s {1,0}; for (auto x : std::views::split(v,s)) { fmt::print("{} ",x); } // [2,6] [9,5,3] [7]
    // using pipeline notation: for (auto x : v | std::views::split(1)) { fmt::print("{} ",x); } // [2,6] [0,9,5,3] [0,7]
    // using an explicit view object: std::ranges::split_view dv {v,1}; for (auto x : dv) { fmt::print("{} ",x); } // [2,6] [0,9,5,3] [0,7]

    lazy_split lazy_split C++20

    The main difference to split is that lazy_split only generates output elements if they are requested/accessed through an iterator into the range object returned by lazy_split.

    #include <ranges>
    std::vector<int> v {2,6,1,0,9,5,3,1,0,7};
    
    for (auto x : std::views::lazy_split(v,1)) { fmt::print("{} ",x); } // [2,6] [0,9,5,3] [0,7]
    std::array<int,2> s {1,0}; for (auto x : std::views::lazy_split(v,s)) { fmt::print("{} ",x); } // [2,6] [9,5,3] [7]
    // using pipeline notation: for (auto x : v | std::views::lazy_split(1)) { fmt::print("{} ",x); } // [2,6] [0,9,5,3] [0,7]
    // using an explicit view object: std::ranges::lazy_split_view dv {v,1}; for (auto x : dv) { fmt::print("{} ",x); } // [2,6] [0,9,5,3] [0,7]

    chunk chunk C++23

    #include <ranges>
    std::vector<int> v {0,1,2,3,4,5,6,7};
    
    for (auto x : std::views::chunk(v,2)) { fmt::print("{} ",x); } // [0,1] [2,3] [4,5] [6,7]
    for (auto x : std::views::chunk(v,3)) { fmt::print("{} ",x); } // [0,1,2] [3,4,5] [6,7]
    // using pipeline notation: for (auto x : v | std::views::chunk(3)) { fmt::print("{} ",x); } // [0,1,2] [3,4,5] [6,7]
    // using an explicit view object: std::ranges::chunk_view sv3 {v,3}; for (auto x : sv3) { fmt::print("{} ",x); } // [0,1,2] [3,4,5] [6,7]

    chunk_by chunk_by C++23

    #include <ranges>
    std::vector<int> v {1,2,0,2,4,5,8,4,6,3,5,2,4};
    
    for (auto x : std::views::chunk_by(v,std::ranges::less{})) { fmt::print("{} ",x); } // [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]
    // custom predicate: auto const diff_less3 = [] (int x, int y) { return std::abs(x-y) < 3; }; for (auto x : std::views::chunk_by(v,diff_less3)) { fmt::print("{} ",x); } // [1,2,0,2,4,5] [8] [4,6] [3,5] [2,4]
    for (auto x : std::views::chunk_by(v,std::ranges::less{})) { fmt::print("{} ",x); } // [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]
    // using pipeline notation: for (auto x : v | std::views::chunk_by(std::ranges::less{})) { fmt::print("{} ",x); } // [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]
    // using an explicit view object: std::ranges::chunk_by_view sv3 {v,std::ranges::less{}}; for (auto x : sv3) { fmt::print("{} ",x); } // [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]

    Multiple Ranges Single Range

    join join C++23

    #include <ranges>
    std::vector<std::string> v = {"I","am","here","now"};
    fmt::print("{}\n", std::views::join(v)); // Iamherenow
    
    // using pipeline notation: fmt::print("{}\n", v | std::views::join); // Iamherenow
    // using an explicit view object: std::ranges::join_view fv {v}; fmt::print("{}\n", fv); // Iamherenow

    join_with join_with C++23

    #include <ranges>
    std::vector<std::string> v = {"I","am","here"};
    fmt::print("{}\n", std::views::join_with(v,'#')); // I#am#here
    
    std::vector<std::vector<int>> a {{7},{3,3,7},{2,6}}; std::vector<int> b {0,1}; fmt::print("{}\n", std::views::join_with(a,b)); // 7 0 1 3 3 7 0 1 2 6
    // using pipeline notation: fmt::print("{}\n", v | std::views::join_with('#')); // I#am#here
    // using an explicit view object: std::ranges::join_with_view fv {v,'#'}; fmt::print("{}\n", fv); // I#am#here

    zip zip C++23

    #include <ranges>
    std::string s1 = "abcd";
    std::vector<int> v = {0,1,2,3,5};
    fmt::print("{}\n", std::views::zip(s1,v));
    // (a,0) (b,1) (c,2) (d,3)
    
    std::array a {0,1}; std::string s2 = "AB"; std::string s3 = "-+"; fmt::print("{}\n", std::views::zip(a,s2,s3)); // (0,A,-) (1,B,+)
    // using an explicit view object: std::ranges::zip_view fv {s1,v}; fmt::print("{}\n", fv); // (a,0) (b,1) (c,2) (d,3)

    zip_transform zip_transform C++23

    #include <ranges>
    std::string s = "abcd";
    std::vector<int> v = {0,1,2,3,4,5};
    auto const f = [] (char c, int x) { return c + std::to_string(x); };
    
    for (auto x : std::views::zip_transform(f,s,v)) { fmt::print("{} ",x); } // ["a0","b1","c2","d3"]
    // using an explicit view object: std::ranges::zip_transform_view av {f,s,v}; for (auto x : av) { fmt::print("{} ",x); } // ["a0","b1","c2","d3"]

    cartesian_product cartesian_product C++23

    #include <ranges>
    std::vector a {'a','b'};
    std::vector b {1,2,3,4};
    std::string c = "♥♣♦";
    for (auto p : std::views::cartesian_product(a,b,c)) {   fmt::print("{} ",p); }
    /* [a,1,♥] [a,1,♣] [a,1,♦] [a,2,♥] [a,2,♣] [a,2,♦] [a,3,♥] [a,3,♣] [a,3,♦] [a,4,♥] [a,4,♣] [a,4,♦] [b,1,♥] [b,1,♣] [b,1,♦] [b,2,♥] [b,2,♣] [b,2,♦] [b,3,♥] [b,3,♣] [b,3,♦] [b,4,♥] [b,4,♣] [b,4,♦] */
    

    Helpers

    subrange subrange C++20

    standard library range view 'subrange' visual example

    Combines an iterator and a sentinel (or two iterators) into a view.

    #include <ranges>
    std::vector<int> v {9,1,3,7,4,5,3,8};
    auto const srv = std::ranges::subrange(v.begin()+2, v.begin()+6);
    for (int x : srv) { std::cout << x << ' '; }
    // 3 7 4 5
    for (int x : std::views::reverse(srv)) { std::cout << x << ' '; }
    // 5 4 7 3
    

    enumerate enumerate C++20

    standard library range view 'enumerate' visual example

    enumerate might not be available yet in the latest g++/libstdc++

    #include <ranges>
    std::string s = "ABCD";
    for (auto const& [index, value] : std::views::enumerate(s)) {
      std::cout << "@" << index << ":" << value << '\n';
    }
    // @0:A @1:B @2:C @3:D
    
    std::vector<std::string> words = {"This", "is", "a", "test."}; for (auto const& [index, value] : std::views::enumerate(words)) { std::cout << "@" << index << ":" << value << '\n'; } // @0:This @1:is @2:a @3:test.

    Tuple Projections

    keys keys C++20

    #include <ranges>
    std::vector<std::pair<char,int>> v {{'b',3},{'a',4},{'z',2},{'k',9}};
    
    fmt::print("{}\n", std::views::keys(v)); // [b,a,z,k]
    // using pipeline notation: fmt::print("{}\n", v | std::views::keys); // [b,a,z,k]

    values values C++20

    #include <ranges>
    std::vector<std::pair<char,int>> v {{'b',3},{'a',4},{'z',2},{'k',9}};
    
    fmt::print("{}\n", std::views::values(v)); // [3,4,2,9]
    // using pipeline notation: fmt::print("{}\n", v | std::views::values); // [3,4,2,9]

    elements elements C++20

    #include <ranges>
    std::vector<std::tuple<char,int,std::string>> v {
      {'b',3,"hearts"}, {'a',4,"clubs"}, {'z',2,"spades"}, {'k',9,"diamonds"} };
    
    fmt::print("{}\n", std::views::elements<2>(v)); // hearts clubs spades diamonds
    // using pipeline notation: fmt::print("{}\n", v | std::views::elements<2>); // hearts clubs spades diamonds

    Non-Range Range

    iota iota C++20

    #include <ranges>
    auto i37 = std::views::iota(3,8);
    for (int x : i37) { std::cout << x << ' '; }
    // 3 4 5 6 7
    
    auto i17 = std::views::iota(1) | std::views::take(7); for (int x : i17) { std::cout << x << ' '; } // 1 2 3 4 5 6 7

    repeat repeat C++23

    #include <ranges>
    auto const r51 = std::views::repeat(1,5);
    for (int x : r51) { std::cout << x << ' '; }
    // 1 1 1 1 1
    
    auto const r4a = std::views::repeat('a') | std::views::take(4); for (char x : r4a) { std::cout << x << ' '; } // a a a a

    istream istream C++20

    #include <ranges>
    std::istringstream isstr {"9 1 3 7 4"};
    auto iv = std::views::istream<int>(isstr);
    for (int x : iv) { std::cout << x << ' '; }
    

    empty empty C++20

    #include <ranges>
    auto ev = std::views::empty<int>;
    fmt::print("{}\n", ev.size());  // 0
    fmt::print("{}\n", ev.empty()); // true
    fmt::print("{}\n", ev.begin() == ev.end()); // true
    

    single single C++20

    #include <ranges>
    auto sv = std::views::single(5);
    fmt::print("{}\n", sv);         // [5]
    fmt::print("{}\n", sv.size());  // 1
    fmt::print("{}\n", sv.empty()); // false
    fmt::print("{}\n", std::distance(sv.begin(),sv.end())); // 1
    

    Special Adaptors

    ref_view ref_view C++20

    #include <ranges>
    std::vector<int> v {9,1,3,7};
    auto rv = std::ranges::ref_view(v);
    fmt::print("{}\n", rv); // [9,1,3,7]
    v.pop_back();
    fmt::print("{}\n", rv); // [9,1,3]
    

    owning_view owning_view C++20

    #include <ranges>
    // make a view passing an rvalue
    auto ov = std::ranges::owning_view(std::vector<int>{9,1,3,7});
    // 'ov' owns the passed-in vector now
    fmt::print("{}\n", ov); // [9,1,3,7]
    

    all all C++20

    #include <ranges>
    std::vector<int> v {9,1,3,7,4};
    auto vw1 = std::views::all(v);
    // 'vw1' is a ref_view
    fmt::print("{}\n", vw1); // [9,1,3,7,4]
    v.pop_back();
    fmt::print("{}\n", vw1); // [9,1,3,7]
    
    // move 'v' into a new view: auto vw2 = std::views::all(std::move(v)); v = std::vector<int>{8}; // => 'vw2' is an owning_view // which stores the elements fmt::print("{}\n", vw2); // [9,1,3,7] fmt::print("{}\n", v); // [8]

    common common C++20

    #include <ranges>
    class Gadget {
      std::array<int,6> a_ {5,4,3,2,1,0};
    public:
      auto begin () const {
        // exclude last element
        return std::counted_iterator{a_.begin(),5};
      }
      auto end () const {
        // counting_iterator already knows size
        return std::default_sentinel;
      }
    };
    Gadget g;
    fmt::print("{}\n", g);
    // accumulate requires same type for 'begin' and 'end'
    // ERROR: std::accumulate(g.begin(), g.end(), 0);
    auto cv = std::views::common(g);
    auto sum = std::accumulate(cv.begin(), cv.end(), 0);
    fmt::print("sum: {}\n", sum);
    

    Cheat Sheet

    (click for fullscreen view)