Your title doesn't match your question
Your title asks "Is it advisable to use Ranges at all?" but in your question you indicate that you're considering using range-v3 — should you use range-v3 or C++20 Ranges?
That's like asking "Is it advisable to use ASIO at all?" and then indicating that you're trying to choose between Boost.ASIO and standalone ASIO. If one is trying to choose between those options, one's clearly already decided to "use ASIO at all," hasn't one? So in your case, you seem to have already decided to "use Ranges at all," and now we're just haggling over the price.
My answer below is therefore aimed probably not at you, but at the hypothetical reader who's wondering whether to introduce C++20 Ranges into a codebase that isn't already fundamentally built around Ranges.
No and yes; or, "You can't avoid Ranges."
It really depends on what you're going to be using Ranges for. IMO it is fairly evident by now that "range-view-ifying" ordinary business-logic code is a bad idea, both for understandability and performance. For example, please don't change
for (int i : selected_indices) {
if (products[i].price > 10) {
std::cout << products[i].name;
}
}
into
std::ranges::copy(
selected_indices
| std::views::filter([](auto& p) { return p.price > 10; })
| std::transform(&Product::name),
std::ostream_iterator<std::string_view>(std::cout)
);
However, it is perfectly reasonable to change
int expected[] = {1,2,3,4,5};
EXPECT_TRUE(std::equal(actual.begin(), actual.end(), expected, expected+5));
into
int expected[] = {1,2,3,4,5};
EXPECT_TRUE(std::ranges::equal(actual, expected);
That's also "C++20 Ranges code." But this time it's actually improving the readability of the code. (It's still increasing the compile-time cost, and leaves the runtime cost unchanged.)
Also, if you are already writing C++98-STL-style "algorithms" via generic programming, you should definitely adopt C++20's modifications to the iterator model, so that your algorithms will work both with old-style iterators and with new-style iterators. That is, I think it could be worthwhile to rewrite a utility library of the form
template<class It, class Pred>
bool my::is_uniqued(It first, It last, Pred pred) {
for (auto it = first; it != last; ++it) {
if (pred(*first, *std::next(first))) {
return false;
}
}
return true;
}
into something more C++20-friendly like
template<class It, class Sent, class Pred>
bool my::is_uniqued(It first, Sent last, Pred pred) {
for (auto it = first; it != last; ++it) {
if (pred(*first, *std::next(first))) {
return false;
}
}
return true;
}
template<std::ranges::range R>
bool my::is_uniqued(R&& rg) {
return my::is_uniqued(rg.begin(), rg.end());
}
This is kind of like when you update a const string&
-taking function to take string_view
instead, thus permitting it to take more kinds of string-like arguments. We're updating the is_uniqued
function to take more kinds of iterable-range-like arguments. This could be seen as a benefit to "code hygiene."
In this example I can't think of any particular reason to start using std::ranges::next
in place of std::next
; and you probably shouldn't add constraints like
template<std::forward_iterator It, std::sentinel_for<It> Sent, std::predicate<std::iter_reference_t<It>> Pred>
bool my::is_uniqued(It first, Sent last, Pred pred) {
because that'll just trash your compile times, and your compiler-diagnostic spew when something goes wrong. It also runs the risk of making your template unusable for some existing hand-coded C++98 iterator type that fails to satisfy some detail of std::forward_iterator
. (The most common way for this to happen, in my experience, is that someone forgot to const-qualify its operator*()
. I would consider such an iterator type defective, and worth fixing, but you might not have the time or permission to go fix it right away.) It also opens the bikeshed door: "Why did you write std::iter_reference_t
instead of std::iter_value_t
? Should we maybe constrain on both?" Having a blanket style rule that "we don't unnecessarily constrain our templates" can shortcut a lot of bikeshedding.
On the other hand, if you're producing a library that currently hand-codes a lot of enable_if
s specifically to emulate what Ranges does natively... well, of course it will be beneficial to use C++20 Ranges instead of that!