Why use std::ranges algorithms over regular algorithms?
Asked Answered
D

3

8

cppreference states:

The ranges library is an extension and generalization of the algorithms and iterator libraries that makes them more powerful by making them composable and less error-prone.

The library creates and manipulates range views, lightweight objects that indirectly represent iterable sequences (ranges).

It mentions using range views, which are, as cppreference states:

The range concept defines the requirements of a type that allows iteration over its elements by providing an iterator and sentinel that denote the elements of the range.

But from an outside perspective, it just seems like a wrapper of an iterator with a concept. So the main question is:

  • What are the problems with using regular algorithms that the ranges library solves (code examples will be appreciated), and when should you use it?
Dittany answered 10/3, 2023 at 10:21 Comment(4)
You can get more naturally looking syntax, e.g. from the linked cppreference page: return std::forward<R>(r) | std::views::take(3) | std::views::reverse;, for (int i : ints | std::views::filter(even) | std::views::transform(square)). But obviously since range is just a pair of iterators you can always get the same result with regular functions taking a pair of iterators.Amorphous
except for the slightly shorter form and the fact that, as there is less to type, there is less chance of making an error.Pooka
ranges algorithm have concept, so provide generally better error messages when misused. the concept are generally stronger than the one implicitly expected from iterator alternative (whereas operator < is enough for iterator version, ranges version might require totally_ordered concept (so <, >, <=, >=, ==, !=))Scammony
This question is being discussed on MetaHesperus
C
8

The second one will allow you to pipe other operations present only in the ranges header, which is much better for composability:

#include <ranges>
#include <vector>
#include <algorithm>
#include <iostream>

int main()
{
    const auto v = std::vector{ 1, 2, 3, 4, 5 };
    const auto is_odd = [](const auto n){ return n % 2 == 1; };
    const auto square = [](const auto n){ return n * n; };
    const auto print = [](const auto n){ std::cout << n << " ";};
    std::ranges::for_each(
        v | std::ranges::views::filter(is_odd) | std::ranges::views::transform(square),
        print); // prints: 1 9 25 
}
Culverin answered 10/3, 2023 at 11:28 Comment(2)
To be fair you can also use pipes with normal std::for_each, but you'd have to create another temporary variable. It's just so much easier to write with std::ranges::for_each.Airdrop
@ago that's true, you're right :)Culverin
S
5

what are the problems with using regular iterators that ranges library solves (code examples will be appreciated)?

It might avoid some mistakes with temporary container:

// assuming std::vector<int> compute_vector();

// Following is wrong begin and end refers to different (temporary) containers
const bool KO = std::is_sorted(compute_vector().begin(),
                               compute_vector().end()); // WRONG

// Here all is ok.
const bool OK = std::ranges::is_sorted(compute_vector());
Scammony answered 10/3, 2023 at 13:33 Comment(0)
A
0

The constrained algorithms in namespace std::ranges are niebloids, which means - among other things - that you can pass the entire algorithm as a function parameter, or assign it to a variable.

void do_work( auto algorithm, int &a, int &b, float &c, float &d )
{
    algorithm( a, b );
    algorithm( c, d );
}

I could pass std::ranges::swap as the first parameter to this function, but no form of std::swap would work.

Albaalbacete answered 21/8, 2024 at 13:49 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.