0% found this document useful (0 votes)
2 views

effective_ranges_cppcon2023

This document is a tutorial on using C++2x ranges, showcasing the differences between traditional methods and the new ranges approach for sorting, finding, and filtering data. It explains the concepts of ranges, algorithms, views, and adaptors, emphasizing their improved usability and performance. The document includes examples, mechanics, and practical applications of ranges in C++ programming.

Uploaded by

tho
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

effective_ranges_cppcon2023

This document is a tutorial on using C++2x ranges, showcasing the differences between traditional methods and the new ranges approach for sorting, finding, and filtering data. It explains the concepts of ranges, algorithms, views, and adaptors, emphasizing their improved usability and performance. The document includes examples, mechanics, and practical applications of ranges in C++ programming.

Uploaded by

tho
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 56

E ective Ranges: A

tutorial for using


C++2x ranges
Je Garland
Created: 2023-10-03 Tue 08:50
1
Intro
the beginning of the end – for begin and end

2.1
hello ranges

2.2
the old way: sort
std::array<int, 6> a { 6, 2, 3, 4, 5, 1 };

std::sort ( a.begin(), a.end() );

for ( auto v : a )
cout << v << " " << endl;

2.3
the ranges way: sort
namespace rng = std::ranges;

std::array<int, 6> a { 6, 2, 3, 4, 5, 1 };

rng::sort ( a ); //clear, obvious meaning, -13 characters

std::print( "{}", a ); //[1, 2, 3, 4, 5, 6]

https://godbolt.org/z/o478PEMff

2.4
the old way: find_if
std::array<int, 6> a { 6, 2, 3, 4, 5, 1 };

auto is_six = [](int i) -> bool { return i == 6; };

// so many beginings and endings


auto i = std::find_if( v.begin(), v.end(), is_six );

if (i != std::end( v ) )
{
cout << "i: " << *i << endl;
}

2.5
the ranges way: find_if
namespace rng = std::ranges;

std::array<int, 6> a { 6, 2, 3, 4, 5, 1 };

auto is_six = [](int i) -> bool { return i == 6; };

auto i = rng::find_if( a, is_six ); //no begin-end

if ( i != rng::end( a ) )
{
std::print( "i: {}\n", *i ); //i: 6
}

https://godbolt.org/z/4KEM3W939

2.6
the ranges way: filter_view
std::array<int, 6> a { 6, 2, 3, 4, 5, 1 };

using std::views::filter;
auto divisible_by3 = filter([](int i) -> bool { return i%3 == 0; } );

std::print("{}", a | divisible_by3 ); //[6, 3]

https://godbolt.org/z/bnchn9jaf

2.7
talk outline
range basics & concepts
range algo details and survey
range views details and survey
adjuncts
observations & futures

2.8
Range Basics

3.1
ranges, algorithms, views, adaptors
range: something that can be iterated over
range algo: algorithm that takes range
view: lazy range thats cheap (to copy)
range adaptor: make a range into a view

3.2
mechanics - headers, namespaces
#include <ranges> // new header for ranges and views

namespace std {
namespace ranges {...}; //improved algorithms and views
namespace ranges::views {...}; //range adaptors
namespace views = ranges::views; //shortcut to adaptors
}

namespace rng = std::ranges; //some examples in this talk

3.3
what's a range?
iterator pair ex: { rbegin, rend }
an object with begin(), end() that returns
iterators
technically an iterator and a sentinel
sentinel and iterator can be different types

3.4
Examples of ranges
standard library things
containers: array, vector, map, set, list
container adaptors: queue, stack,
priority_queue
strings: string and string_view
fs::directory_iterator, stream iterators,
regex iterators
span, mdspan
many libraries outside std

3.5
boost::flat_map example
flat_map<string, int> fm;
fm["world"] = 2;
fm["hello"] = 1;

std::print("{}\n", fm); // {"hello": 1, "world": 2}

for ( auto [k, v] : rng::reverse_view{fm} )


{
std::print("{}:{}\n", k, v );
}
//world:2
//hello:1

https://godbolt.org/z/Mdqh58zrM

3.6
range algorithms
are like STL algorithms
not always a drop in replacement
execute immediately
iteration is interally controlled by algorithm
added projection arguments
return values improved

3.7
range concepts: sized, etc
range - provides a begin iterator and an end sentinel
input_range - speci es that a range has
InputIterator
sized_range - speci es that a range knows its size
in constant time
random_access_range - speci es a range whose
iterator type satis es random_access_iterator
contiguous_range - speci es a range whose
iterator type satis es contiguous_iterator
3.8
Range Algorithms Details
and Survey

4.1
range algorithms are familiar
derived from the STL algorithms, but better
improved return information compared to STL for
some algos
speci ed with concepts

4.2
cheatsheet

4.3
cheatsheet

4.4
projection parameters
ltering predicate independent of function being
invoked
comes for the Adobe Source Libraries (ASL)

4.5
for example - sort with projection
struct stuff {
int idx = 0;
string s;
};

vector<stuff> vstuff = {{2, "foo"}, {1, "bar"}, {0, "baz"}};

//lambda form
ranges::sort(vstuff, std::less<>{},
[](auto const& iii) { return iii.idx; });

//short form
ranges::sort( vstuff, std::less<>{}, &stuff::idx );

https://godbolt.org/z/8cvoEvd11

4.6
improved return values
principle of not discarding calculation
many ranges algorithm return a struct with
iterator+info
find_last example
nd with reverse iterators is suprisingly dif cult
boost.algorithm has one already call
find_backward
what should the return value be?

4.7
find_last example
std::string path = "/foo/bar/baz";

auto leaf = rng::find_last(path, '/')


| std::views::drop(1);
std::print("{}\n", std::string_view(leaf)); // baz

path = "none";
leaf = rng::find_last(path, '/')
| std::views::drop(1); //empty range
std::print("empty: {}", std::string_view(leaf)); //empty:

returns a range
https://godbolt.org/z/8KPjqYrG1

4.8
Views and Adaptor Details

5.1
views are ranges with 'lazy evaluation'
non-owning of elements (mostly)
all methods O(1) - copy and assignment
allows for composition of several processing steps
allows for 'in nite ranges'
because: iteration is externally driven
functional or declarative style versus imperative

5.2
constructed filter_view example
int main() {

std::vector<int> vi{ 0, 1, 2, 3, 4, 5, 6 };

auto is_even = [](int i) { return 0 == i % 2; };

ranges::filter_view evens{vi, is_even}; //no computation

for (int i : evens)


{
std::cout << i << " "; //0 2 4 6
}
}

5.3
range adaptors -> views from ranges
transform Range to View - with custom behaviors
adaptors support the piping operator
pipe syntax allows for 'unix pipe/powershell' type
composition
range adaptors are declared in namespace
std::ranges::views.

5.4
views::filter adpator example
std::vector<int> vi{ 0, 1, 2, 3, 4, 5, 6 };

auto is_even = [](int i) { return 0 == i % 2; };

// view on stack with adaptor


auto evens = vi | rng::views::filter( is_even );

for (int i : evens)


{
std::cout << i << " ";
}
cout << "\n";

5.5
range adaptors execution trace
vector<int> vec_int{ 0, 1, 2, 3, 4, 5 };

//annotated
auto even = [](int i) -> bool { cout << "ev:" << i << "\n";
return 0 == i % 2; };
auto square = [](int i) -> int { cout << "sq:" << i << "\n";
return i * i; };

for (int i : vec_int


| ranges::views::filter(even)
| ranges::views::transform(square)) //how many executions
{
cout << "for: "<< i << "\n"; //how many executions?
}
}

https://godbolt.org/z/1863ce6sW
5.6
range adaptors execution trace - output
// ev:0
// sq:0
// for: 0
// ev:1
// ev:2
// sq:2
// for: 4
// ev:3
// ev:4
// sq:4
// for: 16
// ev:5

for: <- 3
square <- 3
even <- 6

5.7
Types in a view chain - under the hood
//typename magic
template <class T>
constexpr
std::string_view
type_name()
{
//gcc only version see https://stackoverflow.com/questions/81870/
std::string_view p = __PRETTY_FUNCTION__;
return std::string_view(p.data() + 49, p.find(';', 49) - 49);
}

5.8
Types in a view chain - under the hood
vector<string> vs{"hello", " ", "ranges", "!"};

cout << type_name<decltype(vs)>() << "\n";


//vector<basic_string<char> >

auto jv = join_view{vs};
cout << type_name<decltype(jv)>() << "\n";
//ranges::join_view<ranges::ref_view<vector<basic_string<char> > > >

take_view tv{jv, 2};


cout << type_name<decltype(tv)>() << "\n";
//ranges::take_view<ranges::join_view<
// ranges::ref_view<vector<basic_string<char> > > > >

https://godbolt.org/z/G5PE614ff

5.9
views and const iteration
perhaps suprisingly shouldn't pass by const&
why? views like filter_view cache
prefer to pass by value or forwarding reference
https://quuxplusone.github.io/blog/2023/08/13/non-
const-iterable-ranges/
template<std::ranges::range R>
void doit( const R& range); // nope

template<std::ranges::range R>
void doit( R&& range); // yes!

5 . 10
composition: views and range algorithm
example
//utility functions
auto print_int = [](int i) { cout << i << " "; };
auto is_even = [](int i) { return i % 2 == 0; };

vector<int> v { 6, 2, 3, 4, 5, 6 };

//view is defined -- no calculation performed


auto after_leading_evens = rng::views::drop_while(v, is_even);

rng::for_each(after_leading_evens, print_int); //now drive the iteratio


cout << endl;

https://godbolt.org/z/c137bM8f4

5 . 11
Survey of Views - by example

6.1
cheatsheet

6.2
cartesian_product example
(23)
/* output
0 2
0 3
1 2
1 3
*/
namespace rv = ranges::views;

std::vector<int> v1 { 0, 1 };
std::vector<int> v2 { 2, 3 };
for (auto&& [a,b] : rv::cartesian_product(v1, v2))
{
std::print("{} {}\n", a, b);
}

https://godbolt.org/z/rYehsTMxY 6.3
chunk_by (23) example
std::vector v = {1, 2, 2, 3, 0, 4, 5, 2};
// [[1, 2, 2, 3], [0, 4, 5], [2]]
std::print("{}\n", v | std::views::chunk_by( ranges::less_equal{} ));

6.4
zip and zip_transform (23)
bind multiple ranges with pairwise matching
especially important for dealing tuple and pair

6.5
zip example
std::vector v1 = {1, 2};
std::vector v2 = {'a', 'b', 'c'};
std::vector v3 = {3, 4, 5};

// {(1, 'a'), (2, 'b')}


std::print("{}\n", std::views::zip(v1, v2));
// {3, 8}
std::print("{}\n", std::views::zip_transform(std::multiplies(), v1, v3)
// {('a', 'b'), ('b', 'c')}
std::print("{}\n", v2 | std::views::pairwise);
// {7, 9}
std::print("{}\n", v3 | std::views::pairwise_transform(std::plus()));

6.6
Adjuncts

7.1
span
span is a non-owning 'view' over contiguous
sequence of object
cheap to copy - implementation is a pointer and size
constant time complexity for all member functions
de ned in header <span>
unlike most 'view types' can mutate
constexpr ready

7.2
span construction
vector<int> vi = { 1, 2, 3, 4, 5 };
span<int> si ( vi );

array<int, 5> ai = { 1, 2, 3, 4, 5 };
span<int> si2 ( ai );

int cai[] = { 1, 2, 3, 4, 5 };
span<int> si3( cai );

7.3
span as a function parameter
void print_reverse(span<int> si) { //by value
for ( auto i : ranges::reverse_view{si} ) {
cout << i << " " ;
}
cout << "\n";
}

int main () {

vector<int> vi = { 1, 2, 3, 4, 5 };
print_reverse( vi ); //5 4 3 2 1

span<int> si ( vi );
print_reverse( si.first(2) ); //2 1
print_reverse( si.last(2) ); //5 4

7.4
ranges::to
std::vector<int> numbers = {1, 2, 3, 4, 5};

auto squared = numbers


| ranges::views::transform( [](int n) { return n * n; } )
| ranges::to<std::deque>(); // Convert to a deque<int>

std::print("{}", squared); //[1, 4, 9, 16, 25]

https://godbolt.org/z/c137bM8f4

7.5
ranges::to string example
namespace rv = ranges::views;

vector<string> vs = {"foo", "bar", "baz", ""};

string s = vs | rv::join(string_view(" -- "))


| ranges::to<string>();
cout << s << "\n"; //foo -- bar -- baz --

auto is_not_empty = [](const string& s) { return !s.empty(); };

s = vs | rv::filter(is_not_empty)
| rv::join('-')
| ranges::to<string>();
cout << s << "\n"; //foo-bar-baz

https://godbolt.org/z/bd558h47j

7.6
range constructors and other
operations
collections and string gain range constructors
insert_range, assign_range, append_range,
prepend_range
https://wg21.link/P1206
template<container-compatible-range<T> R>
constexpr vector(from_range_t, R&& rg);

7.7
writing a view
write your own
make it pipeable
takes a lot of boiler plate
https://www.boost.org/doc/libs/1_83_0/doc/html/stl_i

7.8
Observations and Future

8.1
sum of squares
auto square = [](int x) {return x*x;};
auto add = [](int x, int y) {return x+y;};

auto square_view = rv::iota(0) //infinite range


| rv::transform(square)
| rv::take(5);

int total = rng::fold_left( square_view, 0, add );

print("{}\n", total); //30

// [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16)]
print("{}\n", square_view | rv::enumerate );

https://godbolt.org/z/qPh1hjdzo

8.2
C++26
algos: reduce, sum, product
views: concat, cycle, getlines
views: drop_last, take_last, drop_exactly,
take_exactly
views: split_when
views: remove, remove_if, replace,
replace_if
views: transform_join
and a lot more…
https://wg21.link/P2214 8.3
advice
always use range algorithms rst
constrained, better errors
projections, superior results
view adapters rst
don't be evil and write inscrutable pipelines

8.4
nal thoughts
Ranges is key building blocks for the future
overall: nicer cleaner more capable code
begin ing not an end

8.5
Finish
ranges::single_view ssv{" Thank You!! \n"s};
for (string s : ssv) cout << s;

8.6

You might also like