Search code examples
c++c++20c++-conceptsrange-v3

What's the point of `viewable_range` concept?


[range.refinements]

The viewable_­range concept specifies the requirements of a range type that can be converted to a view safely.

It's mandatory implementation roughly states that a range further satisfies viewable_range if either

  1. it's simply a view, e.g. std::string_view, or
  2. it's an lvalue reference (even when its reference-removed type is not a view), e.g. std::vector<int>&, or
  3. it's a movable object type (i.e. not reference type), e.g. std::vector<int>

My questions are:

  1. What idea does this concept capture? Specifically, how could its instance "be converted to a view safely" and why do I want such conversion? What does "safety" even mean here?
  2. Do you always use viewable_range to constrain universal reference? (i.e. the only chance T to be deduced to lvalue reference type.) This is the case for standard range adaptor (closure) objects.
  3. Range adaptor (closure) objects are modified to take a range as first parameter in C++23. Is there other usage for viewable_range concept since then?
  4. For application development, when to use viewable_range instead of view or range?

Solution

  • What idea does this concept capture? Specifically, how could its instance "be converted to a view safely" and why do I want such conversion? What does "safety" even mean here?

    Any range can be converted to a view, simply by doing this:

    template <input_range R>
    auto into_view(R&& r) {
        return ref_view(r);
    }
    

    But this isn't really a great idea. If we had an rvalue range (whether it's a view or not), now we're taking a reference to it, so this could dangle if we hold onto the resulting view too long.

    In general, we don't want to hold onto references to views - the point of a view is that range adaptors hold them by value, not by reference. But also in general, we don't want to hold non-view ranges by value, since those are expensive to copy.

    What viewable_range does is restrict the set of ranges to those where we can covert to a view without added concern for dangling:

    • views should always be by value - so an lvalue view is only a viewable_range if it is copyable. An rvalue view is always a viewable_range (because views have to be movable).
    • an lvalue non-view range is always a viewable_range because we take a ref_view in that case. This of course has potential for dangling, but we're taking an lvalue, so it's the safer case.
    • an rvalue non-view range was originally rejected (because our only option was ref_view and we don't want to refer in this case), but will start to be captured as owning_view (as of P2415).

    So basically, the only thing that isn't a viewable range is an lvalue non-copyable view, because of the desire to avoid taking references to views.

    Do you always use viewable_range to constrain universal forwarding reference? (i.e. the only chance T to be deduced to lvalue reference type.) This is the case for standard range adaptor (closure) objects.

    No. Only if what you want to do with the forwarding reference is convert it to a view and store the resulting view. Range adaptors do this, but algorithms don't need to - so they shouldn't use that constraint (none of the standard library algorithms do).

    Range adaptor (closure) objects are modified to take a range as first parameter in C++23. Is there other usage for viewable_range concept since then?

    The term range adaptor closure object is relaxed because now that we can have user-defined range adaptor closure objects (P2387), we can't really enforce what it is that they actually do.

    But the standard library range adaptor closure objects still do require viewable_range (by way of all_t).

    For application development, when to use viewable_range instead of view or range?

    This is really the same question as (2). If what you want is to take any range and convert it to a view to be stored, you use viewable_range - so when you're writing a range adaptor.