Date: Tue, 26 Aug 2025 12:26:45 -0400
On Tue, Aug 26, 2025 at 10:51 AM 梁家铭 via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> Hi,
> Recently I find that it's illegal to write std::generator code like:
> std::generator<int> Test()
> {
> std::vector<int> vec{ 0, 1, 2 };
> co_yield std::ranges::elements_of(vec);
> }
>
> which is very counter-intuitive. This code example is even adopted from
> the std::generator proposal (P2502R2, page 16), which also thinks it should
> be legal. In other words, the proposal seems to be inconsistent in
> intention and wording, so I think there should be a DR to fix it.
>
> Let me briefly analyze what happens first. Remember that here
> std::generator<int>::yielded == int&&.
>
> So in current specification of std::generator, the standard regulates that
> for .yield_value() of the promise type (see [coro.generator]
> <https://eel.is/c++draft/coro.generator#promise-13>):
> *Effects*: Equivalent to:auto nested = [](allocator_arg_t, Alloc, ranges::
> iterator_t<R> i, ranges::sentinel_t<R> s) -> generator<yielded, void,
> Alloc> { for (; i != s; ++i) { co_yield static_cast<yielded>(*i); } };
> return yield_value(ranges::elements_of(nested( allocator_arg, r.allocator,
> ranges::begin(r.range), ranges::end(r.range))));
> So here it's legal, since static_cast<yielded>(*i) (i.e.static_cast<int&&>(int&))
> is legal. However, the constraints of .yield_value() reject it:
> template<ranges::input_range
> <https://eel.is/c++draft/range.refinements#concept:input_range> R, class
> Alloc> requires convertible_to
> <https://eel.is/c++draft/concept.convertible#concept:convertible_to><
> ranges::range_reference_t<R>, yielded> auto yield_value(ranges::
> elements_of<R, Alloc> r);
>
> Here ranges::range_reference_t<R> is int&, and yielded is int&&. That is,
> int& is explicitly convertible to int&& (which is fine for std::generator),
> but not implicitly convertible to int&&, making the constraint fail.
> So I think there should be a DR to loosen the constraint to make explicit
> conversion enough.
> [This email is also sent to author of P2502]
>
I don't know much about this stuff, but it seems like you're right.
All of this stuff was introduced in the C++23 cycle, right? There's nothing
about your example that requires C++26 (and thus no urgency to *fix* it
within the C++26 cycle that is about to close)?
Seems like this could be handled via an LWG issue, if someone were to
request one.
I notice that both of these tweaked examples do compile, not that either of
them is desirable:
std::generator<int> Test2() {
std::vector<int> vec = { 0, 1, 2 };
co_yield std::ranges::elements_of(vec | std::views::as_rvalue);
}
std::generator<float> Test3() { // hat tip to Timur Doumler for
publicizing this quirk of reference binding
std::vector<int> vec = { 0, 1, 2 };
co_yield std::ranges::elements_of(vec);
}
–Arthur
std-proposals_at_[hidden]> wrote:
> Hi,
> Recently I find that it's illegal to write std::generator code like:
> std::generator<int> Test()
> {
> std::vector<int> vec{ 0, 1, 2 };
> co_yield std::ranges::elements_of(vec);
> }
>
> which is very counter-intuitive. This code example is even adopted from
> the std::generator proposal (P2502R2, page 16), which also thinks it should
> be legal. In other words, the proposal seems to be inconsistent in
> intention and wording, so I think there should be a DR to fix it.
>
> Let me briefly analyze what happens first. Remember that here
> std::generator<int>::yielded == int&&.
>
> So in current specification of std::generator, the standard regulates that
> for .yield_value() of the promise type (see [coro.generator]
> <https://eel.is/c++draft/coro.generator#promise-13>):
> *Effects*: Equivalent to:auto nested = [](allocator_arg_t, Alloc, ranges::
> iterator_t<R> i, ranges::sentinel_t<R> s) -> generator<yielded, void,
> Alloc> { for (; i != s; ++i) { co_yield static_cast<yielded>(*i); } };
> return yield_value(ranges::elements_of(nested( allocator_arg, r.allocator,
> ranges::begin(r.range), ranges::end(r.range))));
> So here it's legal, since static_cast<yielded>(*i) (i.e.static_cast<int&&>(int&))
> is legal. However, the constraints of .yield_value() reject it:
> template<ranges::input_range
> <https://eel.is/c++draft/range.refinements#concept:input_range> R, class
> Alloc> requires convertible_to
> <https://eel.is/c++draft/concept.convertible#concept:convertible_to><
> ranges::range_reference_t<R>, yielded> auto yield_value(ranges::
> elements_of<R, Alloc> r);
>
> Here ranges::range_reference_t<R> is int&, and yielded is int&&. That is,
> int& is explicitly convertible to int&& (which is fine for std::generator),
> but not implicitly convertible to int&&, making the constraint fail.
> So I think there should be a DR to loosen the constraint to make explicit
> conversion enough.
> [This email is also sent to author of P2502]
>
I don't know much about this stuff, but it seems like you're right.
All of this stuff was introduced in the C++23 cycle, right? There's nothing
about your example that requires C++26 (and thus no urgency to *fix* it
within the C++26 cycle that is about to close)?
Seems like this could be handled via an LWG issue, if someone were to
request one.
I notice that both of these tweaked examples do compile, not that either of
them is desirable:
std::generator<int> Test2() {
std::vector<int> vec = { 0, 1, 2 };
co_yield std::ranges::elements_of(vec | std::views::as_rvalue);
}
std::generator<float> Test3() { // hat tip to Timur Doumler for
publicizing this quirk of reference binding
std::vector<int> vec = { 0, 1, 2 };
co_yield std::ranges::elements_of(vec);
}
–Arthur
Received on 2025-08-26 16:26:59