Home > Net >  Why does c for each loops accept r-values but std::ranges do not?
Why does c for each loops accept r-values but std::ranges do not?

Time:03-20

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.

  • Related