This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Ready status.
co_yield
ing elements of an lvalue generator
is unnecessarily inefficientSection: 26.8.5 [coro.generator.promise] Status: Ready Submitter: Tim Song Opened: 2023-03-04 Last modified: 2024-06-28
Priority: 3
View other active issues in [coro.generator.promise].
View all other issues in [coro.generator.promise].
View all issues with Ready status.
Discussion:
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.
[2023-03-22; Reflector poll]
Set priority to 3 after reflector poll.
[St. Louis 2024-06-28; move to Ready]
Proposed resolution:
This wording is relative to N4928.
Modify 26.8.5 [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
-11- Returns: An awaitable object of an unspecified type (7.6.2.4 [expr.await]) into which*this
is at the top of*active_
of some generator objectx
. The coroutine referred to byg.range.coroutine_
is suspended at its initial suspend point.g.range
is moved, whose memberawait_ready
returnsfalse
, whose memberawait_suspend
pushesg.range.coroutine_
into*x.active_
and resumes execution of the coroutine referred to byg.range.coroutine_
, and whose memberawait_resume
evaluatesrethrow_exception(except_)
ifbool(except_)
istrue
. Ifbool(except_)
isfalse
, theawait_resume
member has no effects. -12- Remarks: A yield-expression that callsthis functionone of these functions has typevoid
(7.6.17 [expr.yield]).