Back to TILs

C++ Ranges — Part 2

Date: 2022-12-13Last modified: 2023-03-22

The input vector:

  std::vector vec{ 1, 2, 3, 4, 5, 6 };

A key feature of views is that whatever transformation they apply, they do so at the moment you request an element, not when the view is created.

  auto v1 = std::views::reverse( vec );

Here v1 is a view; creating it neither changes vec, nor does v1 store any elements. The time it takes to construct v1 and its size in memory is independent of the size of vec.

  std::cout << "v1.begin = " << *v1.begin() << '\n';
  std::cout << "vec.rbegin = " << *vec.rbegin() << '\n';

This will print 6, but the important thing is that resolving the first element of v to the last element of vec happens on-demand. This guarantees that views can be used as flexibly as iterators, but it also means that if the view performs an expensive transformation, it will have to do so repeatedly if the same element is requested multiple times.

Changes on vector reflect on views

  vec[5] = 60;
  std::cout << "v1.begin = " << *v1.begin() << '\n';
  std::cout << "vec.rbegin = " << *vec.rbegin() << '\n';

std::views::reverse is not the view itself, it’s an adaptor that takes the underlying range (in our case the vector) and returns a view object over the vector. The exact type of this view is hidden behind the auto statement. This has the advantage, that we don’t need to worry about the template arguments of the view type, but more importantly the adaptor has an additional feature: it can be chained with other adaptors!

  auto v2 = vec | std::views::reverse | std::views::drop( 2 );
  std::cout << "v2.begin() = " << *v2.begin() << '\n';

It will print 4, because 4 is the 0-th element of the reversed string after dropping the first two.

  auto v3 = vec //
            | std::views::filter( []( auto const i ) { return i % 2 == 0; } )
            | std::views::transform( []( auto const i ) { return i * i; } );
  std::cout << "v3.begin() = " << *v3.begin() << '\n'; // prints 4

  vec[1] = 16;                                         // 2 -> 16
  std::cout << "v3.begin() = " << *v3.begin() << '\n'; // prints 256
  auto v4 = vec | std::views::reverse | std::views::drop( 2 );

  *v4.begin() = 42; // now vec == {1, 2, 3, 42, 5, 6 } !!
  std::cout << "vec[3] = " << vec[3] << '\n';

Output

v1.begin = 6
vec.rbegin = 6
v1.begin = 60
vec.rbegin = 60
v2.begin() = 4
v3.begin() = 4
v3.begin() = 256
vec[3] = 42

References