A statement like this compiles without error:
for (int i : std::vector<int>({0, 1}))
std::cout << i;
But a statement like this does not:
std::vector<int>({0, 1}) |
std::views::filter([](int i) { return true; });
Why are r-values allowed in for each loops but not in std::range pipes? Is there a way I could get something like the second to work, without having to declare a variable?
CodePudding user response:
The for-loop works fine because the vector<int>
(rvalue) is valid while the loop is evaluated.
The second code snippet has 2 issues:
- The predicate must return a
bool
- the
vector<int>
you are using in the views object is dangling by the time it is evaluated, so the compiler does not accept it.
If instead of a vector<int>
rvalue, the object cannot dangle (lvalue) or holds references into something else that cannot not dangle, this would work.
For example:
auto vec = std::vector<int>({0, 1});
auto vw = vec | std::ranges::views::filter([](int i) { std::cout << i; return true; });
Or you can pass an rvalue that has references to a non-dangling vector<int>
object, like std::span
:
auto vec = std::vector<int>({0, 1});
auto vw = span{vec} | std::ranges::views::filter([](int i) { std::cout << i; return true; })
Here, std::span
is still a rvalue, but the compiler accepts it because the author of std::span
has created an exception for it.
For a custom type with references into something else (typically views), you can create your own exception by creating a specialization of the template variable enable_borrowed_range
.
In the case of vector it would look like this (but don't do this!):
template<> // don't do this
inline constexpr bool ranges::enable_borrowed_range<std::vector<int>> = true;
Your code now compiles but it will trigger undefined behavior because the vector is dangling once the view is evaluated.
CodePudding user response:
This works with latest MSVC STL and libstdc (demo), thanks to P2415R2.
Prior to P2415R2, filter_view
always stores the underlying vector
by reference (via a ref_view
). It was deemed unwise to store a copy of vector
. Because a view is supposed to be cheap to copy, cheap to move, and cheap to destroy. Storing a copy of vector
would have unexpected performance penalty.
And due to lifetime issues, it doesn't make sense to have a reference to rvalue vector
.
But P2415R2 points out that a view can as well be non-copyable, while still be cheap to move and reasonably cheap to destroy.
So P2415R2 introduced owning_view
, a non-copyable view that stores a copy of a range rvalue, and made standard range adaptors use that view.
The end result is, creating a filter_view
from a vector
rvalue works with standard library implementations that implement P2415R2.