Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should i use range adaptors vs range algorithms?

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?

like image 568
Cortex0101 Avatar asked Mar 31 '26 18:03

Cortex0101


2 Answers

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.

like image 50
Barry Avatar answered Apr 03 '26 07:04

Barry


More generally, why prefer meow or meow_view? The meow version is more efficient for many algorithms. When necessary meow calls meow_view to do the actual work. But the work isn't always needed.

Have a look at take in the standard. In section 2 is a list of times when take can shortcut and not invoke take_view. One obvious reason is when take sees an empty range. While take_view may dispatch this quickly, view can avoid constructing and destroying the templates necessary for take_view.

IMO, reading a pipeline from left to right is more natural than the inside out of nested function calls.

You also avoid typing 5 characters.

like image 30
rm1948 Avatar answered Apr 03 '26 06:04

rm1948



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!