P2415R2: What is a view
?
P2415R2 is a proposal for C++20 that relaxes the move
concept to support types that are not constant-time destructible while still guarantee the cheap construction of range adaptors. It introduces a owning_view
class template, enabling views::all
to support movable rvalue non-view
ranges.
- P0896R4 introduces the
view
concept. It states that a typeT
modelsview
if it's default constructible, constant-time copyable, and constant-time movable. - P1456R1 relaxes the
view
concept. It states that a typeT
modelsview
if it's default constructible, constant-time movable, and constant-time destructible. IfT
is copyable, it should be constant-time copyable. - P2325R3 relaxes the
view
concept. It states that a typeT
modelsview
if it's constant-time movable and constant-time destructible. IfT
is copyable, it should be constant-time copyable.
Motivation
The initial motivation of the constant-time copyable constraint is that the range-based algorithms might take views by value. However, if they are implemented as this, they won't be able to operate directly on ranges. (For example, ranges::reverse(v)
would be invalid if v
is a std::vector<T>
.) Therefore, they are revised to take ranges by forwarding references.
template <ranges::bidirectional_range R>
requires std::permutable<ranges::iterator_t<R>>
constexpr ranges::borrowed_iterator_t<R> reverse(R &&r);
The author states that the actual motivation for the constant-time copyable constraint is that range adaptors, such as transform_view
, still take views by value. If the constraint is relaxed, constructing a chain of range adaptors might be an expensive operation. Because the range adaptors are lazily-evaluated, their constructions should be cheap. Otherwise, users bear the runtime cost without actual work being done at that point. The author concludes that the goal of the view
concept is to guarantee the cheap construction of range adaptors.
Proposal
The author proposes a scenario that satisfies the cheap construction of range adaptors while violating the existing constraints of the view
concept. For example, the bad_view
class is non-copyable, constant-time movable, and linear-time destructible, which doesn't model view
. However, constructing a range adaptor from a bad_view
is no more expensive than constructing one from a ref_view<vector<uint64_t>>
. Moreover, a linear-time destruction of vector<uint64_t>
is required in either case, so bad_view
's linear-time destructor doesn't affect the performance.
class bad_view : public std::ranges::view_interface<bad_view> {
public:
bad_view(std::vector<std::uint64_t> v) : data_(std::move(v)) {}
bad_view(bad_view const &) = delete;
bad_view(bad_view &&) = default;
bad_view &operator=(bad_view const &) = delete;
bad_view &operator=(bad_view &&) = default;
auto begin() { return data_.begin(); }
auto end() { return data_.end(); }
private:
std::vector<std::uint64_t> data_;
};
static_assert(std::ranges::view<bad_view>);
The author concludes that the requirement of constant-time destruction can be relaxed without violating the goal of the view
concept. The requirement is reformulated such that if N
moves are made from an object of type T
that containes M
elements, then these N
objects have O(N + M)
destruction. It implies that the moved-from object is constant-time destructible.
Based on the revisited requirement, the author proposes to introduce the owning_view
, which is a view
that has unique ownership of a range. It is movable and stores the range within it. It will still guarantee the cheap construction of range adaptors. Moreover, the author proposes to revise the view::all
range adaptor so that it will capture the ownership of movable rvalue non-view
ranges through owning_view
. The viewable_range
concept is updated to accept this type of range.
In the end, the author attempts to define what is a view
. This is exemplified through the code snippet: auto rng = v | views::reverse;
. If v
is an lvalue, the decision hinges on whether rng
should copy v
or refer to it. If copying v
is preferred because it's a cheap operation and it can avoid potential dangling, then v
is considered a view
. Conversely, if referencing v
is more practical, particularly when copying v
is expensive, then v
is not a view
. For instance, string_view
is a view
, while string
is not.