June 3, 2022·4 min read·engineeringcppcoroutinesranges

P2502R2 defines the ownership rules of std::generator

A synchronous generator needs range semantics that match a uniquely owned coroutine state. The June proposal refines iterator validity and yielded references while preserving single-pass iteration.

A coroutine can suspend after producing a value, but that language mechanism alone does not provide a standard range interface. Casey Carter's P2502R2, dated June 3, proposes std::generator as the library component connecting synchronous coroutine production to input ranges. Its design must describe who owns the suspended computation and what an iterator's reference means while that computation advances.

The proposal calls the generator a move-only view with move-only iterators. The reason is ownership of the coroutine state. A coroutine handle may itself be copyable, but that does not mean the computation it identifies can be duplicated. Exposing ordinary independent iterator copies would imply guarantees the uniquely owned suspended state does not provide.

RAII remains my strongest argument for C++. A generator is interesting on that basis because suspended execution still needs an owner with a defined destruction operation. Making iteration convenient should not conceal the lifetime of the computation that supplies the values.

A suspended function supplies the sequence

The proposed interface lets a function return a generator and use co_yield to supply elements. This finite example illustrates the proposal without depending on indefinite arithmetic or on a library implementation being widely available today.

#include <generator>

std::generator<int> small_counts()
{
    co_yield 1;
    co_yield 2;
}

The returned object represents the coroutine-backed input sequence. Consuming its iterator advances the computation through yielded values. The producer does not first need to materialize a container holding every element. That gives a range algorithm an input interface while preserving the producer's local control flow.

Single-pass behaviour is part of this model. An input range does not promise arbitrary independent traversals of the same computation. A consumer cannot assume it can save copies of positions and replay the generator later as though it were a container. The coroutine's state changes as iteration proceeds.

Reference types determine copying behaviour

The first template argument is not merely a promise that every dereference returns an independent value. In the current design, generator<T> has reference type T&& when T is not already a reference. An explicitly supplied reference type retains its role. The associated value type strips the relevant qualifiers when the second argument is left at its default.

That choice reflects revision history. The preceding P2502R1 records the direction toward the rvalue-reference default, and R2 preserves discussion of earlier designs as explicitly obsolete material. Reading only those older rationale sections would produce an incorrect account of the current interface.

The paper explains a previous performance trap in returning values on every dereference. A generator of strings could create copies even when the consumer only needed to inspect the current yielded object. The revised reference model avoids imposing that particular copying policy on every use.

Yielding an lvalue through a generator with an rvalue-reference interface has its own rule. When the lvalue is copyable, the proposal copies it into temporary storage and lets dereference refer to that copy as an xvalue. The distinction matters because the producer's expression category and the consumer's reference category are not always identical.

Value type and reference type can differ

The second template parameter permits a distinct value type. P2502R2 motivates this through proxy references and through a generator that exposes string views while its owning value type is string. A view of the current data and a value that survives independently are different things.

Its string example reuses a local string while producing views. A container conversion choosing string views could retain references invalidated as iteration continues. Choosing an owning string value type gives generic machinery the information needed to build independent values. The proposal does not make borrowed references permanent by placing them inside a generator.

I prefer that distinction to be explicit in a range interface. A convenient loop can make temporary references look like stored values. The type information should help consumers choose ownership when their results need to outlive the producer's current suspension point.

Revision details protect the input contract

R2 fixes an elements_of example that reused the same input range. The revision history notes that such reuse could cause undefined behaviour for some input ranges. The corrected example chooses between two uses instead. This is a concrete reminder that single-pass requirements apply to the proposal's examples as well as its formal declarations.

The revision also specifies that moving a generator does not invalidate iterators. Ownership can transfer without moving the coroutine's existing state to another address. That guarantee concerns a move of the owning generator, not advancement or destruction of the underlying computation.

Other changes refine preconditions for iterator dereference and increment, including the suspension state of the active coroutine. Together these rules define when the iterator can observe a yielded value and when it can resume production. The proposed convenience comes from a library type that implements those state transitions, with the input-range limitations remaining visible to its callers.