In ranges library there are two kind of operations:
- views which are lazy and require the underlying container to exist.
- actions which are eager, and produce new containers as a result (or modify existing ones)
Views are lightweight. You pass them by value and require the underlying containers to remain valid and unchanged.
From the ranges-v3 documentation
A view is a lightweight wrapper that presents a view of an underlying
sequence of elements in some custom way without mutating or copying
it. Views are cheap to create and copy and have non-owning reference
semantics.
and:
Any operation on the underlying range that invalidates its iterators or sentinels will also invalidate any view that refers to any part of that range.
The destruction of the underlying container obviously invalidates all iterators to it.
In your code you are specifially using views -- You use ranges::views::transform
. The pipe is merely a syntactic sugar to makes it easy to write the way it is. You should look at the last thing in the pipe to see what you produce - in your case, it is a view.
If there was no pipe operator, it would probably look something like this:
ranges::views::transform(my_custom_rng_gen(some_param), my_transform_op)
if there were multiple transformations connected that way you can see how ugly it would get.
Thus, if my_custom_rng_gen
produces some kind of a container, that you transform and then return, that container gets destroyed and you have dangling references from your view. If my_custom_rng_gen
is another view to a container that lives outside these scopes, everything is fine.
However, the compiler should be able to recognize that you are applying a view on a temporary container and hit you with a compile error.
If you want your function to return a range as a container, you need to explicitly "materialize" the result. For that, use the ranges::to
operator within the function.
Update: To be more rexplicit regarding your comment "where does the documentation says that composing range / piping takes and stores a view?"
Pipe is merely a syntactic sugar to connect things in an easy-to-read expression. Depending on how it is used, it may or may not return a view. It depends on the right-hand side argument. In your case it is:
`<some range> | ranges::views::transform(...)`
So the expression returns whatever views::transform
returns.
Now, by reading the documentation of the transform:
Below is a list of the lazy range combinators, or views, that Range-v3 provides, and a blurb about how each is intended to be used.
[...]
views::transform
Given a source range and a unary function, return a new range where each result element is the result of applying the unary function to a source element.
So it returns a range, but since it is a lazy operator, that range it returns is a view, with all its semantics.