void foo(const auto& collection)
{
*collection.begin() = 104;
}
int main()
{
std::vector<int> ints {1, 2, 3, 4, 5};
foo(ints); // Error, as it should be
foo(ints | std::views::all); // Compiles and modifies the vector. Why?
return 0;
}
Why is constness of lvalue reference completely ignored if an argument of a function is of type std::view
?
Edit:
If, as you wrote in the comments, const view reference is similar to const pointer in this context, why does the code not compile if the same function takes a view constructed from an rvalue object as an argument?
std::vector<int> getVec()
{
return std::vector{1, 2, 3, 4, 5};
}
void foo(const auto& collection)
{
*collection.begin() = 104; // Error: assignment of read-only location
}
int main()
{
foo(getVec() | std::views::all); // Nope!
return 0;
}
CodePudding user response:
Views, despite the name, need not be non-modifiable. In general, how a class that represents a sequence of contained objects propagates const
depends on what class gets used. And that's where things get weird.
See, the return type of views::all(e)
changes depending on exactly what e
is. If e
is a glvalue (and is not itself a view), then it returns a ref_view
of the range denoted by e
. ref_view
behaves as if it stores a pointer to the range it is given. Of course, if you have an R*
member of a class, the const
equivalent of that is R * const
, not R const*
. So ref_view
cannot propagate const
to the contained range.
However, if e
is a prvalue (and again is not a view), then what all
returns is an owning_view
. This object actually stores a copy of e
(well, it moves from it) as a member. This means that when you get a const owning_view
, that const
is propagated to that member. And since const vector<T>
does propagate const
, so too will a views::all(e)
of a prvalue of vector
.