Is there a way that my macro can detect that it's running in a C++ coroutine? -...
source link: https://devblogs.microsoft.com/oldnewthing/20211011-00/?p=105784
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Is there a way that my macro can detect that it’s running in a C++ coroutine?
Raymond
October 11th, 2021
Say you are writing a macro that wants to behave differently depending on whether it is expanded inside a coroutine or not. Specifically, you want to expand to return
in a regular function, but co_return
in a coroutine.
template<T> T&& TraceValue(T&& v); // NOTE: Just a sketch. A real macro would have to do more work, // but we are focusing on the IF_IN_COROUTINE part. #define TRACE_RETURN() \ TraceExit(); IF_IN_COROUTINE(co_return, return) #define TRACE_RETURN_VALUE(v) \ IF_IN_COROUTINE(co_return TraceExitValue(v), return TraceExitValue(v)) bool TestSomething() { TRACE_ENTER(); TRACE_RETURN_VALUE(IsSomethingReady()); // want "return" } task<bool> TestSomethingAsync() { TRACE_ENTER(); TRACE_RETURN_VALUE(IsSomethingReady()); // want "co_return" }
Is it possible to write the magic IF_IN_COROUTINE
macro which expands either its first or second parameter?
It’s not possible in general, because the decision as to whether a function body is a regular function body or a coroutine function body depends on what is inside the body. Specifically, if the body it contains any co_await
or co_return statements, then it is a coroutine body. Otherwise, it is a regular function body.
Since the macro is expanded as part of the function body, the decision about whether it is a coroutine or not hasn’t yet been made. In fact, the macro’s expansion might be the thing that determines whether the function body is a coroutine!
In the second example above, the function body expands to something like this:
task<bool> TestSomethingAsync() { TraceEnter(__func__, __FILE_, __LINE__); #if in coroutine co_return TraceExitValue(IsSomethingReady()); #else return TraceExitValue(IsSomethingReady()); #endif }
Whether this is a coroutine depends on what the macro chooses!
If the macro detects that this is a coroutine, then the body expands to co_return TraceExitValue(...)
, and it is that co_return
that makes the function body a coroutine. But if the macro detects that it’s not a coroutine, then the body says return TraceExitValue(...)
, and since there is no co_return
or co_await
statement, the function body is a regular function body.
You thought your macro was passively detecting whether it was in a coroutine, but in fact it is actively controlling the decision!
Now, you might think, “Well, can I just base my decision on the function return type?”
Even if you could detect the return type from a macro (I’m not sure you can), that still wouldn’t be good enough. The task<bool>
might support construction from a bool
, say to represent an already-completed task, and therefore both co_return boolValue
and return boolValue
are legal in the function body.
Basically, you are trying to be a passive predictor of a future that you inadvertently influence. That doesn’t work well in science fiction, and it doesn’t work well here either.
Bonus paradox: Imagine writing the opposite macro:
#define TRACE_RETURN_VALUE(v) \ IF_IN_COROUTINE(return TraceExitValue(v), co_return TraceExitValue(v))
This macro tries to be contrary and says, “Use return
if I’m in a coroutine, but co_return
if I’m not.”
We could call this Russell’s macro since it creates a similar paradox:
task<bool> TestSomethingAsync() { TraceEnter(__func__, __FILE_, __LINE__); #if in coroutine return TraceExitValue(IsSomethingReady()); #else co_return TraceExitValue(IsSomethingReady()); #endif }
If the coroutine detector says, “This is a coroutine”, then the macro expands to return
, which makes the function body not a coroutine. But if the coroutine detector says, “This is not a coroutine”, then the macro expands to co_return
, which makes the function body a coroutine after all!
Proof by logical contradiction that a perfect coroutine-detector macro is impossible to write.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK