Title Text

The C++20 Range Library

from Range-v3 to the future

Po Yen Chen @poyenc

© Po Yen Chen 2020​​

Background image ©

What is a range?

  • pair of iterators

  • manipulate elements  through iterators

© Po Yen Chen 2020​​

Background image ©

© Po Yen Chen 2020​​

Background image ©

Different iterator category matters

  • InputIterator  vs OutputIterator

  • InputIterator  vs ForwardIterator

template <typename Iterator> requires std::output_iterator<Iterator, int>
void assign_zero(Iterator it) {
  *it;
  *it = 0;
}
template <typename Iterator> requires std::forward_iterator<Iterator>
void check_multi_pass(Iterator it) {
  decltype(auto) before = *it;
  std::next(it);
  decltype(auto) after = *it;
  assert(std::addressof(before) == std::addressof(after));
}

© Po Yen Chen 2020​​

Background image ©

Liveness of range and iterators

const auto pi = [] {
  std::vector<int> range;
  return begin(range);
}();
// WARNING: its danger to use *pi from now

const auto pc = [] {
  auto range = "hello"sv;
  return begin(range);
}();
// ok to use *pc anywhere
  • range and iterators live independently
  • range may  owns elements

© Po Yen Chen 2020​​

Background image ©

Ranges in concrete

  • Most STL containers e.g. std::vector, std::deque
  • With owning semantics
  • std::basic_string
  • With non-owning semantics
  • std::span
  • std::basic_string_view

© Po Yen Chen 2020​​

Background image ©

Ranges in abstract

std::vector<int> ints(10);
std::iota(begin(ints), end(ints), 1);
  
// create range of descending integers
assert(std::is_sorted(std::make_reverse_iterator(end(ints)),
                      std::make_reverse_iterator(begin(ints)),
                      std::greater<>()));

std::istringstream stream("96 27");

// create range of arbitrary integers
assert(std::accumulate(std::istream_iterator<int>(stream),
                       std::istream_iterator<int>(), 0) == 123);

There are no range-based libraries until C++20

© Po Yen Chen 2020​​

Background image ©

The problem of iterator-based library

std::vector<unsigned> scores;
// fill score values here

decltype(scores) squared;
std::transform(
  begin(scores), end(scores), std::back_inserter(squared), 
  [](auto score) { return std::sqrt(score); }
);

decltype(scores) enlarged;
std::transform(
  begin(squared), end(squared), std::back_inserter(enlarged), 
  [](auto score) { return score * 10; }
);

std::cout << std::count_if(begin(enlarged), end(enlarged),
                           [](auto score) { return 60 <= score; })
          << std::endl;
  • Lots of intermediate values
  • Hard to find business logics

© Po Yen Chen 2020​​

Background image ©

Rewrite by range-based library

std::vector<unsigned> scores;
// fill score values here

std::cout << std::ranges::count_if(
  scores
  | ranges::view::transform([](auto score) { return std::sqrt(score); })
  | ranges::view::transform([](auto score) { return score * 10; })
  , [](auto score) { return 60 <= score; }
) << std::endl;
  • Better maintainability
  • More efficiency

© Po Yen Chen 2020​​

Background image ©

The Range-v3 library contains...

  • Algorithms
  • range-based STL algorithms
  • Actions
  • in-place modifying range-based STL algorithms
  • Views
  • composable adaptations of ranges
  • lazy evaluation

More are coming in C++23

© Po Yen Chen 2020​​

Background image ©

Two more things...

  • The Customization Point

template <typename T>
void two_step_swap(T& a, T& b) {
  using std::swap; // overload functions in namespace std are also candidates
  swap(a, b);      // customization point, may find match through ADL
}

namespace ns {
  struct Foo {};
  void swap(Foo&, Foo&) { // customized function
    puts("customized swap");
  }
}

ns::Foo a, b;
::two_step_swap(a, b); // ok to use qualified name here

© Po Yen Chen 2020​​

Background image ©

Two more things...

  • The Customization Point Object

const std::vector values{1.1, 2.2, 3.3};
std::ranges::transform(values, ranges::ostream_iterator<>(std::cout, " "),
                       std::floor);
                    // ^^^^^^^^^^ error: unresolved overloaded function type

modified version

inline constexpr struct {
  template <typename T>
  auto operator()(T value) const {
    using std::floor;
    return floor(value); // may invoke a customized function for T
  }
} two_step_floor;

std::ranges::transform(values, ranges::ostream_iterator<>(std::cout, " "),
                       two_step_floor);

© Po Yen Chen 2020​​

Background image ©

The example: ranges::view::cycle

  • Generate ring-buffer access indices
#include <iostream>
#include <ranges>

#include <range/v3/iterator/stream_iterators.hpp>
#include <range/v3/view/cycle.hpp>
#include <range/v3/view/indices.hpp>
#include <range/v3/view/take.hpp>

int main() {
  std::ranges::copy(ranges::view::indices(5)  // ring-buffer size = 5
                    | ranges::view::cycle
                    | ranges::view::take(10), // desired sequence length = 10
                    ranges::ostream_iterator<>(std::cout, " "));
}

Let's dive into the source : cycle.hpp

© Po Yen Chen 2020​​

Background image ©

Inspect the source files

© Po Yen Chen 2020​​

Background image ©

Toward to range

  • More range-based algorithms
  • take single range
  • take iterator & size pair
  • Conversion from range to concrete types
  • use ranges::to() to generate containers
  • add ctor to accept range argument
  • Additional projection parameter for algorithms

© Po Yen Chen 2020​​

Background image ©

References

Made with Slides.com