12

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)

Print

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)

sandthorn
  • 2,770
  • 1
  • 15
  • 59
  • @nicol-bolas I should tag this with `cppcoro` but there is no `cppcoro` tag yet. This might not directly be related to coroutine but at least related to the implementation of cppcoro which is coroutine. – sandthorn Jan 11 '19 at 05:55

1 Answers1

10

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.

Eric Niebler
  • 5,927
  • 2
  • 29
  • 43
  • 1
    Will ranges::view::join also has a plan to change to comply with std::view::join? Or spawning another name for that, at least? The debates of design decision on whether `View` should requires copyability is very interesting, where can we read more about this? BTW, thank you for this very thorough explanation as always. – sandthorn Jan 11 '19 at 05:13
  • 1
    The `std::view::join` behavior is already implemented in range-v3's v1.0-beta branch. Also, cppcoro has already been fixed so that your `gen()` function can return a `generator&&>`. (The author, Lewis Baker, sits next to me at work.) The discussion about the copyability of Views is probably in one of range-v3's issues. Or in the PR to allow move-only views. – Eric Niebler Jan 12 '19 at 00:55