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>
#include <ranges>
#include <vector>
#include <iostream>
int main () {
std::vector<int> v {7,2,6,3,4};
for (int x : std::views::reverse(v)) { std::cout << x <<' '; } // 4 3 6 2 7
std::cout << '\n';
// using pipeline notation:
for (int x : v | std::views::reverse) { std::cout << x <<' '; } // 4 3 6 2 7
std::cout << '\n';
// storing a view in a variable:
auto rv = std::views::reverse(v);
// 'rv' has type 'std::ranges::reverse_view'
for (int x : rv) { std::cout << x <<' '; } // 4 3 6 2 7
std::cout << '\n';
}
#include <ranges>
#include <vector>
#include <iostream>
int main () {
std::vector<int> v {7,2,6,3,4};
std::ranges::reverse_view rv {v};
for (int x : rv) { std::cout << x <<' '; } // 4 3 6 2 7
std::cout << '\n';
}
Composing
Views that take a single input range (and possibly some other non-range parameters)
can be chained with the pipe |
operator:
view1 | view2 | view3 | …
#include <ranges>
#include <vector>
#include <iostream>
int main () {
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) { std::cout << x <<' '; } // 8 2 2 4
std::cout << '\n';
}
#include <ranges>
#include <vector>
#include <iostream>
int main () {
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) { std::cout << x <<' '; } // 3 3 2 9 6
std::cout << '\n';
}
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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)]
}
std::ranges::to
might not be available yet in the latest g++/libstdc++
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
int main () {
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
}
#include <ranges>
#include <vector>
#include <list>
#include <fmt/ranges.h>
int main () {
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)]
}
Selectors
return a range that contains single elements from the input range
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <string>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
Subrange
return a contiguous subrange of the input range
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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.
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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
return a range of either tuples or contiguous subranges of the input range
#include <ranges>
#include <string>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
fmt::print("\n");
for (auto x : std::views::slide(v,3)) { fmt::print("{} ",x); }
// [0,1,2] [1,2,3] [2,3,4]
fmt::print("\n");
// using pipeline notation:
for (auto x : v | std::views::slide(3)) { fmt::print("{} ",x); }
// [0,1,2] [1,2,3] [2,3,4]
fmt::print("\n");
// 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]
fmt::print("\n");
}
#include <ranges>
#include <string>
#include <fmt/ranges.h>
int main () {
std::string s = "abcde";
for (auto p : std::views::adjacent<3>(s)) { fmt::print("{} ",p); } // abc bcd cde
fmt::print("\n");
// using pipeline notation:
for (auto p : s | std::views::adjacent<3>) { fmt::print("{} ",p); } // abc bcd cde
fmt::print("\n");
for (auto p : s | std::views::pairwise) { fmt::print("{} ",p); } // ab bc cd de
fmt::print("\n");
}
F
n Application#include <ranges>
#include <cctype> // std::toupper
#include <string>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <string>
#include <fmt/ranges.h>
int main () {
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>
fmt::print("\n");
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>
fmt::print("\n");
// using pipeline notation:
for (auto p : s | std::views::pairwise_transform(f2)) { fmt::print("{} ",p); }
// <ab> <bc> <cd> <de>
fmt::print("\n");
}
Tokenization
return a range of contiguous subranges of the input range
#include <ranges>
#include <vector>
#include <array>
#include <fmt/ranges.h>
int main () {
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]
fmt::print("\n");
std::array<int,2> s {1,0};
for (auto x : std::views::split(v,s)) { fmt::print("{} ",x); }
// [2,6] [9,5,3] [7]
fmt::print("\n");
// using pipeline notation:
for (auto x : v | std::views::split(1)) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
fmt::print("\n");
// 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]
fmt::print("\n");
}
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>
#include <vector>
#include <array>
#include <fmt/ranges.h>
int main () {
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]
fmt::print("\n");
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]
fmt::print("\n");
// using pipeline notation:
for (auto x : v | std::views::lazy_split(1)) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
fmt::print("\n");
// 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]
fmt::print("\n");
}
#include <ranges>
#include <string>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
fmt::print("\n");
for (auto x : std::views::chunk(v,3)) { fmt::print("{} ",x); }
// [0,1,2] [3,4,5] [6,7]
fmt::print("\n");
// using pipeline notation:
for (auto x : v | std::views::chunk(3)) { fmt::print("{} ",x); }
// [0,1,2] [3,4,5] [6,7]
fmt::print("\n");
// 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]
fmt::print("\n");
}
#include <ranges>
#include <string>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
fmt::print("\n");
// 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]
fmt::print("\n");
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]
fmt::print("\n");
// 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]
fmt::print("\n");
// 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]
fmt::print("\n");
}
Multiple Ranges → Single Range
#include <ranges>
#include <string>
#include <vector>
#include <fmt/ranges.h>
int main () {
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
}
#include <ranges>
#include <string>
#include <vector>
#include <fmt/ranges.h>
int main () {
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
}
#include <ranges>
#include <string>
#include <array>
#include <vector>
#include <fmt/ranges.h>
int main () {
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)
}
#include <ranges>
#include <string>
#include <vector>
#include <fmt/ranges.h>
int main () {
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"]
fmt::print("\n");
// 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"]
fmt::print("\n");
}
#include <ranges>
#include <vector>
#include <string>
#include <fmt/ranges.h>
int main () {
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,♦] */
fmt::print("\n");
}
Helpers
Combines an iterator and a sentinel (or two iterators) into a view.
#include <ranges>
#include <string>
#include <vector>
#include <iostream>
int main () {
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 << ' '; }
std::cout << '\n';
// 3 7 4 5
for (int x : std::views::reverse(srv)) { std::cout << x << ' '; }
std::cout << '\n';
// 5 4 7 3
}
enumerate
might not be available yet in the latest g++/libstdc++
#include <ranges>
#include <string>
#include <vector>
#include <iostream>
int main () {
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::cout << "\n";
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.
}
Projections
take a range of tuples and return a range of single elements from the tuples
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <vector>
#include <string>
#include <fmt/ranges.h>
int main () {
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
→ Rangefactories that generate a range from non-range input
#include <ranges>
#include <string>
#include <iostream>
int main () {
auto i37 = std::views::iota(3,8);
for (int x : i37) { std::cout << x << ' '; }
std::cout << '\n';
// 3 4 5 6 7
auto i17 = std::views::iota(1) | std::views::take(7);
for (int x : i17) { std::cout << x << ' '; }
std::cout << '\n';
// 1 2 3 4 5 6 7
}
#include <ranges>
#include <iostream>
int main () {
auto const r51 = std::views::repeat(1,5);
for (int x : r51) { std::cout << x << ' '; }
// 1 1 1 1 1
std::cout << '\n';
auto const r4a = std::views::repeat('a') | std::views::take(4);
for (char x : r4a) { std::cout << x << ' '; }
std::cout << '\n';
// a a a a
}
#include <ranges>
#include <sstream>
#include <string>
#include <iostream>
int main () {
std::istringstream isstr {"9 1 3 7 4"};
auto iv = std::views::istream<int>(isstr);
for (int x : iv) { std::cout << x << ' '; }
std::cout << '\n';
}
#include <ranges>
#include <string>
#include <fmt/ranges.h>
int main () {
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
}
#include <ranges>
#include <string>
#include <fmt/ranges.h>
int main () {
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
}
Adaptors
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
// 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]
}
#include <ranges>
#include <vector>
#include <fmt/ranges.h>
int main () {
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]
}
#include <ranges>
#include <numeric>
#include <iterator>
#include <fmt/ranges.h>
int main () {
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
Related …
- An Overview of Standard Ranges (Tristan Brindle, 2019)
- Conquering C++20 Ranges (Tristan Brindle, 2021)
- Range Algorithms, Views and Actions - A Comprehensive Guide (Yitzchaki Dvir, 2019)
- From STL to Ranges: Using Ranges Effectively (Garland Jeff, 2019)
- What a View! Building Your Own (Lazy) Range Adaptors [1/2] (Di Bella Chris, 2019)
- What a View! Building Your Own (Lazy) Range Adaptors [2/2] (Di Bella Chris, 2019)
- Using C++20 Ranges Effectively (Jeff Garland, 2019)
Comments…