There are basically several ways that you could conceive of declaring an algorithm that takes some kind of range:
- constrain on
range vs constrain on view
- take by
T&&, T const&, T&, or T
That cartesian product gives you 8 options to consider.
The problem with T const&, even for non-mutating algorithms, is that we have non-const-iterable ranges (range<R> doesn't necessitate range<R const>). There may a hypothetical alternative design where view actually requires const-iteration, but that's not the design we have. So while T const& seems like the obviously right choice for non-mutating algorithms, it ends up limiting functionality a lot.
The problem with T& is that Ranges leads to many situations of having rvalue ranges, and taking a T& would prevent users from writing algo(r | views::transform(f)) -- and it's basically the selling point of the library to be able to write that.
That leaves T and T&&.
By-value range -- template <ranges::range R> void f(R r) -- is problematic since ranges can be arbitrarily expensive to copy (bad for performance) and can be owning (in which case your algorithm probably isn't doing what you think it's doing -- it may do the right thing for some types but not others).
Forwarding-reference view -- template <ranges::view V> void g(V&& v) -- actually disallows passing lvalues, because V& is never a view, so likewise is surprisingly limited in functionality.
This effectively reduces the universe to two options:
template <ranges::range R> void f(R&& r);
template <ranges::view V> void g(V v);
These do have functional differences. If I have a non-copyable view, then f(move_only_view) would work while g(move_only_view) would not. But then the reason you'd have a move-only view is that it's an input view, so perhaps g(std::move(move_only_view)) would be syntactically better anyway. Otherwise, f(copyable_view) doesn't copy the view while g(copyable_view) does. Copying views is cheap, as in doesn't copy all the elements, but it's not necessarily free (consider a transform_view with an arbitrarily fat projection, or just many layers of adaptors). So the forwarding-reference-range version does offer the most functionality, and is the cheapest.
Other than that, these two are quite similar. f(e) and g(views::all(e)) , when they both compile, are basically the same? Which kind of begs the question of why you'd write the value-view version over the forwarding-range one. There's not really a significant motivator for doing so.
The primary place where you need view, specifically, rather than range is when constructing range adaptors. Those need to have member views (not ranges), since they themselves need to be views. But even there, the entry-point isn't:
template <ranges::view V> auto make_adaptor(V );
It's, instead:
template <ranges::viewable_range R> auto make_adaptor(R&& );
That is, we still take a forwarding range - it's a refined version of range that ensures that we can actually convert it to a view.