7

Much of the functionality provided by the various range adaptors seem to be quite similar to the algorithms library.

For example std::ranges::transform_view

A range adaptor that represents view of an underlying sequence after applying a transformation function to each element.

and std::ranges::transform

Applies the given function to a range and stores the result in another range, beginning at result.

If we wanted to turn a string into uppercase we could use both the algorithm and view:

int main() {
    std::string in{ "hello\n" };
    std::string out;

    // using transform view
    std::ranges::copy( std::views::transform(in, toupper), std::back_inserter(out) );
    std::cout << out;

    out.clear();
    // using transform algorithm
    std::ranges::transform(in, std::back_inserter(out), ::toupper);
    std::cout << out;

    return 0;
}

What do the views accomplish that the algorithms can't, and vice versa? When should I prefer one over the other?

Barry
  • 286,269
  • 29
  • 621
  • 977
Cortex0101
  • 831
  • 2
  • 12
  • 28
  • I prefer ``, and in C++23 you don't need to import ``, just `auto out = in | std::views::transform(toupper) | std::ranges::to();` – 康桓瑋 Jul 12 '22 at 15:29
  • 2
    You don't need to persist the range, e.g. `for (char c : std::views::transform(in, toupper)) std::cout << c;` avoids populating `out` – Caleth Jul 12 '22 at 15:50

1 Answers1

8

The short version is:

  • Range algorithms are eager. They do some operation right now.
  • Range adaptors are lazy. They are setting up work to do at a future point. The laziness means that they compose better.

You use whichever makes the most sense for the problem that you're solving. If you're just transform-ing a range into another one, the algorithm is the most direct solution for that. If you're building up a more involved set of operations (maybe you're not just transforming every element, but only some?), then you probably want to build an adaptor pipeline.

It also isn't a one-or-another thing - as your example shows, even when you're using the adaptor (views::transform) you're still also using an algorithm (ranges::copy). Range adaptor pipelines do regularly end in an algorithm. For instance, what if I want the smallest letter (case-insensitive)? I might do that this way:

char smallest = std::ranges::min(
    in | std::views::filter([](unsigned char c){ return std::isalpha(c); })
       | std::views::transform([](unsigned char c){ return std::tolower(c); })
    );

The adaptors help me (lazily) build up a range of lower case letters, and then the algorithm (eagerly) gives me the answer.

Barry
  • 286,269
  • 29
  • 621
  • 977