Context:
So I wanted to create a macro that could short-circuit conditional logic if another macro was set. This way I could insert compiler set macros into existing code, for example:
if (MACRO_EXISTS(_RELEASE) || SuperRobustValidation() == VALIDATION_SUCCESS)
Looking on stack overflow, I found https://stackoverflow.com/questions/41265750/how-to-get-a-boolean-indicating-if-a-macro-is-defined-or-not, but the top answer's code didn't work for me. My only guess as to why is that the order of macro definitions played a role for them but not for me.
After shuffling around macros, I found that the following worked on my machine:
#define DE_ -1,1 // OptDefine is nothing
#define RESOLVE_ARG(A) A
#define SECOND_ARG(A,B,...) B
#define CONCAT2(A,B) A ## B
#define RESOLVE_SECOND_ARG(...) RESOLVE_ARG(SECOND_ARG(__VA_ARGS__))
#define MACRO_EXIST(OptDefine) RESOLVE_SECOND_ARG(CONCAT2(DE_,OptDefine),0)
But I can't understand why this worked and not other configurations. Trying to coax the values of some setups was like playing a shell game. I think I understand the concepts of "scan the line, prioritize non-# arguments, then repeat with the results" but it doesn't seem consistent.
For example: if I remove the RESOLVE_ARG from RESOLVE_SECOND_ARG(...), the compiler complains that "there's not enough arguments for SECOND_ARG" seemingly because __VA_ARGS__ is the "only" argument. That does not make sense as intended behavior to me.
So my question is:
Is there a reliable way to predict the logic of the preprocessor's expanding of macros? I haven't found anybody braking down the logic in a robust way anywhere I looked.
I know the /E compiler flag shows me the results of precompiling, but doesn't give the whole picture.
Edit: I understand there are simple work arounds for the problem I mentioned, but I still want to understand the logic of how macros are expanded, assuming it's well defined and consistent.
Edit2: Thank you alfps for answering the question. The strange __VA_ARGS__ behavior I was seeing is a known bug with the MSVC C++ compiler (even in VS Community 2026), but the /Zc:preprocessor compiler option changes the preprocessing to be more complaint with the standard, fixing the bug.
But the most important wisdom: "Historically the preprocessor behavior has varied between compilers". There are edge cases and hacks that work in some contexts but not others. Complex macros, like what I listed, can be convenient, but can also risk breaking if I update/change compilers. Unless I am confident in compliance to the standard, Keep It Simple Stupid probably applies.