Why macro argument gets evaluated?

Given the following simple code:

set(var [[\$\{something\}]])
function(print_fun VAR)
    message("Fun: {${VAR}}")
endfunction()
macro(print_mac VAR)
    message("Mac: {${VAR}}")
endmacro()
print_fun("${var}")
print_mac("${var}")

It will produce this output:

Fun: {\$\{something\}}
Mac: {${something}}

So it is clear that in the macro our argument changed (got evaluated) and in the function it stayed intact. Could you please point me to the part in the docs which describes this behavior?

https://cmake.org/cmake/help/latest/command/macro.html#arguments

And what exactly explains this behavior in the section you linked in?

When a macro is invoked, the commands recorded in the macro are first modified by replacing formal parameters (${arg1}, …) with the arguments passed, and then invoked as normal commands.

And how does it answer what I asked? Try replacing formal parameters with actual argument just like it says in the docs and see what happens:
message("Mac: {${VAR}}") ==> message("Mac: {\\$\\{something\\}}")
And that should print exactly the same as with function.

Nope, it means that the content of the macro first has a replacement of its formal (named) parameters expanded. Then the code is executed. This is why this code doesn’t work:

macro (foo arg)
  if (arg) # Macro arguments are not variables, so this doesn't care about what it looks like it cares about
    message("arg: ${arg}") # prints `arg: 0` because this is replaced by the macro argument value
  endif ()
endmacro ()

set(arg 1) # What that `if` sees
foo(0)

If foo was instead a function, the argument exists as a variable and the if would end up in the else() branch.

Sorry, but I still fail to see how any of it explains the evaluation of the argument. Let’s get the outer variable out of the way and simplify the calling code to this:

print_fun([[\$\{something\}]])
print_mac([[\$\{something\}]])

So we have a literal replacement in case of macro, so our argument should go directly to where it is mentioned (message("Mac: {${VAR}}")) but somewhere our argument gets evaluated and all ‘\’ get dropped. I don’t see how docs explain it, in my opinion docs are silent about this quirk hence this question: is it a doc deficiency or macro’s behavior is incorrect?

The macro replaces the value in the code, so the code now looks like fun("\$\{expanded_value\}") which is just a string with escapes (which avoid the expansion, but don’t get printed). Given the quoting rules, losing the escapes doesn’t surprise me, but I don’t know which path actually does that. I also don’t know how to write documentation for this beyond what is already stated: macro arguments are expanded and then the code is executed after expansion. I suppose that an explicit note about that this may mean an extra layer of escaping is necessary depending on the usage of the argument in the macro body is useful, but I can see that as just leading to more confusion too.

Really, just avoid macros unless you need the “affects the calling scope” behavior and use functions for everything else.

1 Like