tl;dr
It's not clear to me how ranges::any_view<T, C>
works, especially in relation to what T
is, i.e. a &
, a const&
, a value type, or what else.¹
Research
There isn't really much about it here, yet, nor I have found much around, beside this very useful explanation of why ranges::begin
can't be called on const ranges::any_view
.
A tricky (?) case
Here's one such example which puzzles me a lot, and below are the relavants bit of it. I think that thoroughly understanding why the examples linked below work (or don't) the way they do, should help me understand what any_view
actually is.
This function accepts any range of std::string
s and print
s each of them (the filter(always(true))
is letting everything pass):
void test(any_view<std::string> parts) {
for_each(parts | filter(always(true)), print);
};
Now, if I pass an lvalue of type any_view<std::string const&>
any_view<std::string const&> view{foos};
into it,
test(view);
all is fine, but if I first pipe that into cache1
²,
test(view | cache1);
then the all the std::string
s result empty.
By trial and error I realized that changing
test(any_view<std::string> parts) {
to
test(any_view<std::string const&> parts) {
solves the problem, but I don't really understand why.
But if I then was to also change
any_view<std::string const&> view{foos};
to
any_view<std::string> view{foos};
Furthermore, manually inlining the body of test
at the call site, also seems to solve the issue.
And to make the thing trickier, when I try to change std::string
for a Foo
that holds a std::string
(and sets it to ""
in the destructor, to hopefully make possible UB a bit more "observable"), the issue seems to go away.
Side question
I know there's no any_view
in c++20, and I guess there won't be one in c++23 either, but is there a reason (beside performance) why it's not been included yet or won't be included in the foreseeable future? I mean, to me it looks like it does serve a ligitimate purpose, so what else in the standard library is or will fill that hole?
(¹) It's a bit clearer what the role of C
is, especially because I had an experience of downgrading an any_view<SomeType, sized>
to any_view<SomeType, forward>
for the purpose of allowing storing a filter_view
, which is not sized, into it.
(²) Yes, the cache1
is useless here, but in my real usecase I had view | transform(f) | filter(g)
, so I inserted cache1
in between to prevent f
from being called twice for each entry x
in view
which passes the test g(f(x))
.