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_ 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.
Preprocessor just doesn’t work that way preprocessor doesn’t know anything about functions that’s a later phase of compilation.
__func__has entered the chat.Point of order: That’s a predefined variable not preprocessor. Also shouldn’t we be using
std::source_locationanyway? 😉True, but it’s something that preprocessor macros often take advantage of, so it’s part of the conversation.
Reminds me of the counterproof by contradiction to the halting problem.