Title
co_yielding elements of an lvalue generator is unnecessarily inefficient
Status
voting
Section
[coro.generator.promise]
Submitter
Tim Song

Created on 2023-03-04.00:00:00 last changed yesterday

Messages

Date: 2024-06-28.22:24:33

Proposed resolution:

This wording is relative to N4928.

  1. Modify [coro.generator.promise] as indicated:

    namespace std {
      template<class Ref, class V, class Allocator>
      class generator<Ref, V, Allocator>::promise_type {
      public:
        […]
        auto yield_value(const remove_reference_t<yielded>& lval)
          requires is_rvalue_reference_v<yielded> &&
            constructible_from<remove_cvref_t<yielded>, const remove_reference_t<yielded>&>;
    
        template<class R2, class V2, class Alloc2, class Unused>
          requires same_as<typename generator<R2, V2, Alloc2>::yielded, yielded>
            auto yield_value(ranges::elements_of<generator<R2, V2, Alloc2>&&, Unused> g) noexcept;
        template<class R2, class V2, class Alloc2, class Unused>
          requires same_as<typename generator<R2, V2, Alloc2>::yielded, yielded>
            auto yield_value(ranges::elements_of<generator<R2, V2, Alloc2>&, Unused> g) noexcept;
    
        template<ranges::input_range R, class Alloc>
          requires convertible_to<ranges::range_reference_t<R>, yielded>
            auto yield_value(ranges::elements_of<R, Alloc> r) noexcept;
        […]
       };
    }
    
    […]
    template<class R2, class V2, class Alloc2, class Unused>
      requires same_as<typename generator<R2, V2, Alloc2>::yielded, yielded>
      auto yield_value(ranges::elements_of<generator<R2, V2, Alloc2>&&, Unused> g) noexcept;
    template<class R2, class V2, class Alloc2, class Unused>
      requires same_as<typename generator<R2, V2, Alloc2>::yielded, yielded>
      auto yield_value(ranges::elements_of<generator<R2, V2, Alloc2>&, Unused> g) noexcept;
    

    -10- Preconditions: A handle referring to the coroutine whose promise object is *this is at the top of *active_ of some generator object x. The coroutine referred to by g.range.coroutine_ is suspended at its initial suspend point.

    -11- Returns: An awaitable object of an unspecified type ([expr.await]) into which g.range is moved, whose member await_ready returns false, whose member await_suspend pushes g.range.coroutine_ into *x.active_ and resumes execution of the coroutine referred to by g.range.coroutine_, and whose member await_resume evaluates rethrow_exception(except_) if bool(except_) is true. If bool(except_) is false, the await_resume member has no effects.

    -12- Remarks: A yield-expression that calls this functionone of these functions has type void ([expr.yield]).

Date: 2024-06-28.22:24:33

[ St. Louis 2024-06-28; move to Ready ]

Date: 2023-03-15.00:00:00

[ 2023-03-22; Reflector poll ]

Set priority to 3 after reflector poll.

Date: 2023-03-04.00:00:00

Consider:

std::generator<int> f();
std::generator<int> g() {
    auto gen = f();
    auto gen2 = f();
    co_yield std::ranges::elements_of(std::move(gen));   // #1
    co_yield std::ranges::elements_of(gen2);             // #2
    // other stuff
}

Both #1 and #2 compile. The differences are:

  • #2 is significantly less efficient (it uses the general overload of yield_value, so it creates a new coroutine frame and doesn't do symmetric transfer into gen2's coroutine)

  • the coroutine frame of gen and gen2 are destroyed at different times: gen's frame is destroyed at the end of #1, but gen2's is not destroyed until the closing brace.

But as far as the user is concerned, neither gen nor gen2 is usable after the co_yield. In both cases the only things you can do with the objects are:

  • destroying them;

  • assigning to them;

  • call end() on them to get a copy of default_sentinel.

We could make #2 ill-formed, but that seems unnecessary: there is no meaningful difference between generator and any other single-pass input range (or a generator with a different yielded type that has to go through the general overload) in this regard. We should just make #2 do the efficient thing too.

History
Date User Action Args
2024-11-19 16:09:07adminsetstatus: ready -> voting
2024-06-28 22:24:33adminsetmessages: + msg14226
2024-06-28 22:24:33adminsetstatus: new -> ready
2023-03-22 22:40:39adminsetmessages: + msg13484
2023-03-05 03:48:24adminsetmessages: + msg13446
2023-03-04 00:00:00admincreate