C++ coroutine: lifetime of temporary bound to reference
C++ coroutines materialize temporaries into the coroutine frame and store references there, so references to them remain valid across suspension. Key rules.
What is the lifetime of a temporary bound to a local reference in a C++ coroutine?
On cppreference I read that the coroutine state contains ‘local variables and temporaries whose lifetime spans the current suspension point.’ I don’t understand how this works for references bound to temporaries. Consider this coroutine:
/*...*/ SomeCoroutine()
{
const auto& ref = std::string("abc");
co_yield ref.size(); // assume the coroutine really does suspend
co_return ref.size();
}
Questions:
- Before the coroutine is suspended, is
refa local variable on the stack, or is it a member of the coroutine state (in which case how can it be a reference)? - After the coroutine is resumed, is
refa local variable on the stack, or is it a member of the coroutine state? If it is still a reference, is it dangling now, or was the temporary string copied/moved into the coroutine state?
Is the correct mental model that the materialized temporary itself is stored in the coroutine frame so the reference binds to that object across suspension points?
Yes — in C++ coroutines the materialized temporary that a local reference binds to is placed into the coroutine frame (the coroutine state), and the reference is stored in that frame bound to the frame-stored temporary; as a result the reference does not dangle across suspension points. Both the temporary and the reference are members of the coroutine state (conceptually), and they’re destroyed when the coroutine state is destroyed. So your mental model — “the temporary lives in the coroutine frame and the reference binds to it across suspension” — is correct.
Contents
- How coroutine frames store temporaries and references
- Step‑by‑step: your example explained
- When a temporary is materialized into the frame (rules)
- Authoritative wording and references
- Practical implications and safe patterns
- Sources
- Conclusion
How coroutine frames store temporaries and references
A coroutine has an abstract coroutine state (the coroutine frame) that contains whatever must survive across suspension points: the promise object, parameters, locals and any temporaries whose lifetime must span a suspension. In other words, the compiler arranges that anything that will be used after a suspension point is stored in the frame rather than in the caller’s stack frame — that includes both the temporary object and the reference that binds to it.
Conceptually you can think of the compiler generating a hidden struct roughly like this:
// conceptual only — real compilers may implement differently
struct CoroutineFrame {
promise_type promise;
std::string materialized_temp; // std::string("abc")
const std::string& ref; // reference member bound to materialized_temp
// ...
};
Yes: a reference can be a data member of a class/struct, so the reference itself is stored in the coroutine frame and initialized to refer to the materialized temporary also in the frame. Some compilers might lower that to a pointer internally, but the observable behavior is identical: the temporary is kept alive inside the frame and the reference continues to refer to it across suspensions. See the explanation on cppreference describing that the coroutine state “contains local variables and temporaries whose lifetime spans the current suspension point” (cppreference).
Step‑by‑step: your example explained
Given your code:
SomeCoroutine()
{
const auto& ref = std::string("abc");
co_yield ref.size(); // assume this suspends
co_return ref.size();
}
What actually happens (abstractly):
- When the coroutine begins, the implementation allocates the coroutine state (the frame) and prepares storage for locals/temporaries that must survive suspension. The standard/cppreference describes this allocation as part of starting the coroutine (cppreference).
- The prvalue std::string(“abc”) is materialized into storage that belongs to the coroutine frame because ref is a local that will be used after the suspension. The frame now contains the std::string object.
- The local reference ref becomes a member of the frame and is initialized to bind to that frame-stored std::string. So both objects live in the coroutine state.
- At
co_yield ref.size()the coroutine suspends; the coroutine frame (with the std::string and the reference) remains alive (it’s not on the caller stack), sorefstill refers to a valid std::string. - After resume,
co_return ref.size()reads the same std::string via the same reference; nothing has dangled. - When the coroutine completes or is destroyed, the frame is destroyed and the stored objects (the string, etc.) are destroyed in reverse order of construction.
So answer to your two direct questions:
- Even before the first suspension point the compiler allocates the frame and stores locals that must survive suspension there; conceptually ref is a member of the coroutine state (not a separate stack slot in the caller).
- After resume ref is still a reference member in the coroutine state bound to the materialized temporary; it is not dangling. The temporary was materialized into the frame — nothing magical copies the temporary elsewhere at suspension time, the compiler arranged the temporary to live in frame storage from the start.
For community discussion of the same point see the Stack Overflow thread that walks through the same mental model and examples (Stack Overflow — lifetime of temporary bound to local reference in a coroutine).
When a temporary is materialized into the frame (rules)
The key rule: a temporary is materialized into the coroutine frame only when its lifetime must span a suspension point. If a temporary is only used and destroyed before any suspension point, it need not go into the frame and will be treated like an ordinary temporary (stack/temporary storage) and destroyed in the usual way.
Practical consequences:
- If you bind a local reference to a temporary and then use that reference after a co_await/co_yield that follows the binding, the temporary must be kept across suspension — therefore it will be materialized into the frame.
- If the temporary is only used before the first suspension, the compiler may keep it on the (conceptual) stack and destroy it as usual — no frame allocation required for that temporary.
- Function parameters passed by reference are copied into the coroutine state as references; they still refer to the original argument. If the original argument goes out of scope while the coroutine is suspended, you can get a dangling reference (so be careful with reference parameters).
The Old New Thing blog explains a related point for co_return temporaries: if the temporary does not need to span a suspension point it doesn’t need to go into the frame (and can be constructed/destroyed normally) (Old New Thing).
Authoritative wording and references
Authoritative resources that back up this model:
- The cppreference page on coroutines explains the coroutine state semantics: the implementation allocates the coroutine state and it “contains local variables and temporaries whose lifetime spans the current suspension point” — this is the central sentence explaining the behavior (cppreference).
- Community explanations (detailed examples and Q&A) make the same point: temporaries that must survive suspension are stored in the coroutine frame and references to them remain valid across suspension points (Stack Overflow discussion, another clarifying thread).
- The Microsoft blog post gives an example showing that temporaries not required to survive a suspension are not forced into the frame, reinforcing the “only when needed” rule (Old New Thing).
Read those pages if you want the exact standard-like wording and several worked examples.
Practical implications and safe patterns
A few pragmatic recommendations so you don’t get surprised:
- If a value is small or cheap to move, prefer storing it by value in the coroutine (e.g.,
std::string s = "abc";) — that avoids subtle lifetime reasoning. - Don’t rely on references to caller-owned objects unless you can guarantee the caller’s object outlives the coroutine suspension. If you need shared ownership, use
shared_ptror equivalent. - Be aware that compilers may optimize layout (they might implement the reference as a pointer internally), but they must preserve the abstract lifetime semantics; write code against the abstract rule, not compiler internals.
- If you’re auditing code, look for: references bound to temporaries or to function parameters that might go out of scope while the coroutine is suspended.
Example safe rewrite of your snippet:
SomeCoroutine()
{
std::string s = "abc"; // stored by value in the frame
co_yield s.size();
co_return s.size();
}
That removes any doubt about where the object lives.
Sources
- Coroutines (C++20) - cppreference.com
- Lifetime of temporary to which a local reference is bound in a c++ coroutine - Stack Overflow
- Coroutines (C++20) - cppreference mirror (web.cs.dal.ca)
- Lifetime of local variables in c++ coroutine - Stack Overflow
- C++ coroutines: The lifetime of objects involved in the coroutine function - The Old New Thing
Conclusion
Yes — the correct mental model is that in C++ coroutines the materialized temporary is stored in the coroutine frame (the coroutine state) and the local reference is stored in that frame bound to the materialized temporary; their lifetime is extended to the lifetime of the coroutine state. In short: for coroutines, temporaries that must survive suspension are kept in the frame, so the reference does not dangle across suspension points — but be careful when references point to caller-owned objects that may not outlive the suspension.