First of all I want to predict the memory usage of my code, just as any responsible programmer should. This would apply even if I was not deciding to allocate my coroutine frames using placement new, as I am (see below pseudocode). Even supposing I change my mind about placement-newing all my coroutines, and thus I let the complier allocate all my coroutines on the heap, I'd still want the C++ language to tell me how much heap I'm going to eat up by that.
But, IRL, I'm targeting a high-reliability and embedded environment. There might not even be a heap, so...
struct coroutine_return_type
{
struct promise_type
{
void *operator new(std::size_t sz, char *buf, std::size_t szbuf)
{
if (sz > szbuf)
throw std::bad_alloc{};
return buf;
}
void operator delete(void *)
{
}
// ...
};
// ...
};
coroutine_return_type my_coroutine(char *, std::size_t)
{
// The arguments, char * and std::size_t,
// have been fowrarded to promise_type::operator new
// but here in the coroutine body they aren't used again...
for ( ; ; )
co_yield /* something */;
}
struct coroutine_instance_type
{
char my_coroutine_frame[ /* WHAT? */ ];
coroutine_return_type my_coroutine_instance;
coroutine_instance_type()
: my_coroutine_instance{my_coroutine(my_coroutine_frame, sizeof(my_coroutine_frame))}
{
// ...
}
// ...
};
WHAT I WANT
I want a complie-time expression to return an upper bound on my coroutine size, to replace /* WHAT? */
.
STUPID SOLUTION
There's an obviously stupid way to (not quite) do what I want:
Subclass std::bad_alloc. Then throw std::bad_alloc{}
in my operator new
becomes throw std::my_bad_alloc{sz}
. The catch block can call my_bad_alloc_instance.get_parameter()
to learn what sz
was inside operator new
.
Call my_coroutine(nullptr, 0)
and catch the exception.
What's stupid about this (nonexhaustive list):
It's not a compile-time expression, because it has to "return" its value using a throw
and throw
can never be used in a complie-time expression. But the replacement for /* WHAT? */
in my pseudocode needs to be a compile-time expression.
It's a sample, not an upper bound. Suppose the actual, allocated size of the coroutine frame depends on conditions at run-time. (Now, I don't anticipate that different coroutine sizes for different run-time conditions, will ever actually occur in my IRL applcation, but according to the C++ standard it seems to be possible.) In that case, it's insufficient to just learn what size is actually passed to operator new
. The required expression would have to return, instead, an upper bound on what could be passed to operator new
.
So, in summary:
SUMMARY OF THE QUESTION
What tools does the C++ language provide to query the size of a coroutine frame? The ideal tool should be a compile-time expression for allocating non-heap memory to the coroutine, or alternatively, the same tool would serve as well for bounding the amount of heap.
This was debated at length during the standardization of C++20 coroutines. The layout and size of the coroutine frame cannot be determined until after the optimizer finishes its job, and making that information available to the frontend would require fundamental rearchitecturing of all existing compilers. Implementers reported that not even a (useful) upper bound is feasible.
See parts 2 and 4 of P1365R0 for a discussion on ways to use coroutines in environments where no dynamic memory allocation is allowed.
What tools does the C++ language provide to query the size of a coroutine frame?
None. What you want is impossible by design.
co_await
coroutines in C++ are designed in such a way that being a coroutine is an implementation detail of the function. From just a function declaration, it is impossible to know if a function is a coroutine or if it just happens to have a signature that could use the various coroutine machinery. The feature is meant to work in such a way that it's effectively none of your business if a function is or is not a coroutine.
Being able to determine the size of a coroutine frame would first require being able to identify a coroutine. And since the system is designed such that this is impossible... well, there you are.
As Nicol Bolas mentioned it is not possible to get it as a constexpr value. But this is not possible for "normal functions", too. There is just one rule "don't store big objects on the stack to avoid stackoverflows".
As a role of thumb the maximum of the required heap storage is the size of your local variables that must be available after the first continuation and eventually some small "management-fields" to store the contunation-point (usually some kind of int).
But our compilers are nowadays really smart and optimize the heap allocations completely away - so you should not worry to much about it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With