This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4418. co_yield elements_of(vector<int>()) does not compile

Section: 25.8.5 [coro.generator.promise] Status: New Submitter: Mathias Stearn Opened: 2025-10-16 Last modified: 2025-10-23

Priority: 2

View all other issues in [coro.generator.promise].

View all issues with New status.

Discussion:

The std::generator proposal (P2502) explicitly says that the following example is supposed to work, but it does not compile:

For convenience, we further propose that co_yield elements_of(x) be extended to support yielding the values of arbitrary ranges beyond generators, ie

std::generator<int> f()
{
  std::vector<int> v = /*... */;
  co_yield std::ranges::elements_of(v);
}

This doesn't compile because the overload listed in 25.8.5 [coro.generator.promise] p13 requires convertible_to<ranges::range_reference_t<R>, yielded> (i.e. convertible_to<int&, int&&>) which isn't satisfied.

So it seems clear enough that something got lost in the translation from the design intent to wording. My understanding of the intent is that co_yield elements_of(rng) should be semantically similar to the following code, but require a specific optimization in the case where rng is the same type of generator as the current coroutine:

for (auto&& x : rng) {
  co_yield std::forward<decltype(x)>(x);
}

Note that that code does compile correctly where rng is a vector<int>, so I think the original code should as well.

Speculation on how this happened: Prior to P2529, the generator<T> in P2502R0 was basically the same as generator<const T&> where co_yield elements_of(vector<int>()) does work. After P2529 (which was a design paper w/o wording) was approved by LEWG, P2502R1 was updated to change the reference type to T&&. However, when making the change, I assume that the impact it had on the elements_of-non-generator overload of yield_value was not considered, so it was unmodified.

[2025-10-23; Reflector poll.]

Set priority to 2 after reflector poll.

The current resolution is incorrect, and breaks following example:

std::generator<std::vector<int>> g()
{
  std::vector<int> v = {1, 2, 3};
  co_yield std::ranges::elements_of(v);
} 	

The constraints are equivalent to checking if yield_value(*it) is well-formed, and maybe we could express is directly as:

requires requires (promise_type p, ranges::iterator_t<R> it) {
    p.yield_value(*it);
}

Proposed resolution:

This wording is relative to N5014.

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

    [Drafting note: The following changes would check that co_yield *i would match one of the overloads of yield_value() described in paragraphs 4 or 6. Alternatively this could be expressed more directly as a helper comment or in prose in a Mandates: element.

    namespace std {
      template<class Ref, class Val, class Allocator>
      class generator<Ref, Val, Allocator>::promise_type {
      public:
        […]
        template<ranges::input_range R, class Alloc>
          requires convertible_to<ranges::range_reference_t<R>, yielded> || 
                   (is_rvalue_reference_v<yielded> && constructible_from<remove_cvref_t<yielded>, 
                   const remove_reference_t<yielded>&>)
            auto yield_value(ranges::elements_of<R, Alloc> r);
        […]
      };
    }
    
    […]
    template<ranges::input_range R, class Alloc>
      requires convertible_to<ranges::range_reference_t<R>, yielded> || 
               (is_rvalue_reference_v<yielded> && constructible_from<remove_cvref_t<yielded>, 
               const remove_reference_t<yielded>&>)
      auto yield_value(ranges::elements_of<R, Alloc> r);
    

    -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))));