5

I am learning about ranges and as I learned about range adaptors, they only accept viewable_range as the first argument according to cppreference RangeAdaptorObject.

But when I tested this code on Compiler Explorer:

auto val = std::views::drop(std::vector{1, 2, 3, 4, 5}, 2);

I found that for clang 16.0.0 and gcc 12.0 (both with -std=c++20), this code does compile, while for any older version it does not.

I also tried:

auto val = std::views::transform(std::vector{1, 2, 3, 4, 5}, 2);

This does not compile for any versions of compilers.

So why is it allowed to passing non-viewable_range to some range adaptors in newer version of compiler?

update:

Thanks for @康桓瑋 to point out my typo, views::transform does compile with rvalue movable range:

auto val = std::views::transform(std::vector{1, 2, 3, 4, 5}, [](int i) { return i; });
3
  • 2
    I believe a std::vector<int> is a viewable range. stackoverflow.com/questions/73537755/… Commented Jul 17, 2023 at 2:49
  • @TimRoberts thank you, I see my mistakes here, and it seems that std::views::transform has stricter requirements, it requires the first argument to be a view object, so that is why it does not work for std::views::transform Commented Jul 17, 2023 at 3:09
  • 3
    views::transform takes a callable as a second parameter instead of the integer. So this is just a typo. Commented Jul 17, 2023 at 3:32

1 Answer 1

5

C++20 DR (Defect Report) P2415 enhances the definition of viewable_range to:

template<class T>
  concept viewable_range =
    range<T> &&
    ((view<remove_cvref_t<T>> && constructible_from<remove_cvref_t<T>, T>) ||
     (!view<remove_cvref_t<T>> &&
      (is_lvalue_reference_v<T> || (movable<remove_reference_t<T>> && !is-initializer-list<T>))));

Note the last part:

(!view<remove_cvref_t<T>> &&
  (is_lvalue_reference_v<T> || (movable<remove_reference_t<T>> && !is-initializer-list<T>))));

We no longer require that the non-view range must be an lvalue reference, in which case a new move-only owning_view was introduced to handle such cases.

owning_view will move the rvalue non-view range to its internal member to take ownership of the data, and make the transfer of ownership only through the move operation, which still meets the semantic requirements of the view updated by the paper.

So in C++20, an rvalue vector is also a viewable_range because it can be converted to owning_view.

As your example, views::drop(std::vector{1, 2, 3, 4, 5}, 2) will first move the rvalue vector into owning_view, then moves owning_view into drop_view. Since the drop_view regains ownership of the data, it is also move-only.

Sign up to request clarification or add additional context in comments.

3 Comments

And the owning view will in turn keep the underlying vector alive, as would the returned object?
@Yakk-AdamNevraumont Exactly. owning_view moves the vector to its own member to take ownership of the data and makes transfers of ownership only via moves, which still meets the (updated by P2415) definition of a view.
Can you add this to the answer? It is implied, but the behavior change being linked to the 'can it compile' change makes the change make sense.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.