3

C++20 introduces views::all which is a range adaptor that returns a view that includes all elements of its range argument.

The expression views::all(E) is expression-equivalent (has the same effect) to:

  • decay-copy(E) if the decayed type of E models view.
  • Otherwise, ref_view{E} if that expression is well-formed
  • Otherwise, subrange{E}

The first case represents that a view's type is not changed after being piped with views::all (goldbot):

auto r = views::iota(0);
static_assert(std::same_as<decltype(r), decltype(r | views::all)>);

The second case is used to wrap a viewable_range with ref_view to facilitate range pipe operations:

int r[] = {0, 1, 2};
static_assert(std::same_as<ranges::ref_view<int[3]>, decltype(r | views::all)>);

But regarding the third case, I can't think of under what circumstances subrange{E} is well-formed and ref_view{E} is ill-formed.

What is its purpose? Can someone give an example of it?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90

3 Answers3

4

But regarding the third case, I can't think of under what circumstances subrange{E} is well-formed and ref_view{E} is ill-formed.

ref_view{E} is only well-formed for lvalue ranges.

subrange{E} is only well-formed for borrowed ranges. You can find its deduction guide in [range.subrange.general]:

template<borrowed_­range R>
  subrange(R&&) ->
    subrange<iterator_t<R>, sentinel_t<R>,
             (sized_­range<R> || sized_­sentinel_­for<sentinel_t<R>, iterator_t<R>>)
               ? subrange_kind::sized : subrange_kind::unsized>;

Where a borrowed range is either an lvalue again or a range that opts into being borrowed from. Types like string_view and span are borrowed, for instance.

So if you have something like an rvalue vector<int>, then that's not a view (first bullet) nor can you construct a ref_view from it (because it's an rvalue), nor can you construct a subrange from it (because it's a non-borrowed range).


I realize this doesn't quite answer the question cause I flipped the polarity in my head while typing the answer. But T.C.'s got me covered.

Another purely hypothetical example is a non-view range that stores its contents in a shared_ptr, and then its iterators also share that data. Something like:

struct SharedVector {
    std::shared_ptr<std::vector<int>> data;

    struct Iterator {
        std::shared_ptr<std::vector<int>> data;
        std::vector<int>::iterator cur;

        // ...
    };

    auto begin() -> Iterator { return {data, data->begin()}; }
    auto end() -> Iterator { return {data, data->end()}; }
};

An rvalue SharedVector would not be a view (not O(1)-destructible), you could not ref_view{E} it (because it's an rvalue), but such a range could still be borrowed, so subrange{E} could work.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Why does a view require O(1)-destructible? – xskxzr Aug 16 '21 at 02:41
  • 1
    @xskxzr This is a good question with an [involved answer](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2415r0.html) – Barry Aug 19 '21 at 03:56
  • It is in fact https://cplusplus.github.io/LWG/issue3452. The issue does not specify why a view should be O(1)-destructible. Maybe the reason is the same as that mentioned in P2415 (to make passing-by-value cheap) but that does not convince me---"cheap" does not mean "O(1)". – xskxzr Aug 19 '21 at 05:22
4

Borrowed rvalue non-view ranges (views would fall under the first bullet so that the question doesn't arise).

The canonical example in the current standard is span of nonzero static extent (like span<int, 42>). A hypothetical borrowed range type that doesn't support assignment would be another example. Synthetic examples can be constructed as well (since both borrowed_range and view are opt-in).

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Note since [P2325](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2325r3.html) `std::span` becomes a view. – xskxzr Aug 16 '21 at 02:33
  • Is there any practical use of a borrowed range type that doesn't support assignment? – xskxzr Aug 16 '21 at 02:37
2
template<__NotSameAs<ref_view> T>
requires std::convertible_to<T, R&> && requires { _FUN(std::declval<T>()); }
constexpr ref_view(T&& t);

from this, rvalues seem iffy.

template<class R>
ref_view(R&) -> ref_view<R>;

also looks like it blocks rvalues.

Make an rvalue that you can subrange{E}. Like a subrange rvalue. Probably others, depending on deduction guides of subrange.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I think in that case, `E` is a `viewable_range` which is an rvalue and not a `view`, but I cannot find such `E` that makes the return type of `E | std::views::all` a `subrange`. – 康桓瑋 May 10 '21 at 10:54
  • 1
    I think the `E` can be `std::span{v}`. https://godbolt.org/z/v6o84q111. But this can be improved by Barry's [P2325R0](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2021/p2325r0.html). – 康桓瑋 May 10 '21 at 11:03