Suppose that we have
cppcoro::generator<int> gen_impl(int in) {
const auto upper = in + 10;
for (; in < upper; ++in)
co_yield in;
}
cppcoro::generator<cppcoro::generator<int>> gen() {
for (int n = 1; n < 100; n += 10)
co_yield gen_impl(n);
}
So we can iterate inner range just fine
for (auto&& row : gen() ) {
for (auto n : row)
std::cout << n << ' ';
std::cout << '\n';
}
NOTE: range-for on ref is required because cppcoro::generator
doesn't allow copying (deleted copy ctor)
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
But when we try to "flattern" with view::join
auto rng = gen();
for (auto n : rng | ranges::view::join) {
std::cout << n << '\n';
};
It seems view::join require Copyable inner range?
In file included from <source>:3:
In file included from /opt/compiler-explorer/libs/rangesv3/trunk/include/range/v3/view.hpp:38:
In file included from /opt/compiler-explorer/libs/rangesv3/trunk/include/range/v3/view/for_each.hpp:23:
/opt/compiler-explorer/libs/rangesv3/trunk/include/range/v3/view/join.hpp:320:50: error: call to deleted constructor of 'cppcoro::generator<cppcoro::generator<int> >'
return join_view<all_t<Rng>>{all(static_cast<Rng&&>(rng))};
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/rangesv3/trunk/include/range/v3/view/view.hpp:112:21: note: in instantiation of function template specialization 'ranges::v3::view::join_fn::operator()<cppcoro::generator<cppcoro::generator<int> > &, false, nullptr>' requested here
v.view_(static_cast<Rng&&>(rng))
^
/opt/compiler-explorer/libs/rangesv3/trunk/include/range/v3/utility/functional.hpp:731:42: note: in instantiation of function template specialization 'ranges::v3::view::view<ranges::v3::view::join_fn>::pipe<cppcoro::generator<cppcoro::generator<int> > &, ranges::v3::view::view<ranges::v3::view::join_fn> &, false, nullptr>' requested here
pipeable_access::impl<Pipe>::pipe(static_cast<Arg&&>(arg), pipe)
^
<source>:35:21: note: in instantiation of function template specialization 'ranges::v3::operator|<cppcoro::generator<cppcoro::generator<int> > &, ranges::v3::view::view<ranges::v3::view::join_fn>, false, nullptr>' requested here
for (auto n : rng | ranges::view::join) {
^
/opt/compiler-explorer/libs/cppcoro/include/cppcoro/generator.hpp:174:3: note: 'generator' has been explicitly marked deleted here
generator(const generator& other) = delete;
^
/opt/compiler-explorer/libs/rangesv3/trunk/include/range/v3/view/join.hpp:76:36: note: passing argument to parameter 'rng' here
explicit join_view(Rng rng)
^
What makes this not compiled?
Is there any bug in range-v3 or cppcoro?
Only incompatible design decisions?
godbolt (Full)
In range-v3, a move-only view is OK. That got implemented late and there may still be bugs, but that's not what is happening here.
The first problem is that you are trying to adapt an lvalue of type cppcoro::generator
here:
auto rng = gen();
for (auto n : rng | ranges::view::join) {
Since a generator is a view, the join
view will want to copy it. It can't because it is not copyable.
You can fix this problem by moving the generator in:
auto rng = gen();
for (auto n : std::move(rng) | ranges::view::join) {
Then you run into the next problem, which is that the reference type of generator<generator<int>>
is const generator<int>&
, and you have the same problem again: the join
wants to hold a copy of the inner generator while it iterates over it, but it cannot make a copy.
The workaround is a bit ugly: change the generator to return a non-const lvalue reference:
cppcoro::generator<cppcoro::generator<int>&> gen() {
for (int n = 1; n < 100; n += 10) {
auto tmp = gen_impl(n);
co_yield tmp;
}
}
and then std::move
each inner range with a move
view:
auto rng = gen();
for (auto n : std::move(rng) | ranges::view::move | ranges::view::join) {
std::cout << n << '\n';
}
The result compiles. Whether it runs or not depends on how gracefully cppcoro handles the case where someone steals away the guts of the value that it safely tucked away in the coroutine's promise type.
https://godbolt.org/z/mszidX
A note about the future std::view::join
:
The join
view that will ship with C++20 is a little different. If the outer range's reference type is a real reference (as in this case), it will not try to make a copy of the view to which it refers. That means in C++20, you won't need the ugly view::move
hack.
However, the C++20 View
concept currently requires copyability so this solution still won't work. We have a TODO item to relax this before C++20 ships, but there's no telling how the Committee will like that idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With