Kip Kip - 1 month ago 6
C++ Question

How do I make a C++ macro behave like a function?

Let's say that for some reason you need to write a macro:

MACRO(X,Y)
. (Let's assume there's a good reason you can't use an inline function.) You want this macro to emulate a call to a function with no return value.




Example 1: This should work as expected.



if (x > y)
MACRO(x, y);
do_something();


Example 2: This should not result in a compiler error.



if (x > y)
MACRO(x, y);
else
MACRO(y - x, x - y);


Example 3: This should not compile.



do_something();
MACRO(x, y)
do_something();





The naïve way to write the macro is like this:

#define MACRO(X,Y) \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl;


This is a very bad solution which fails all three examples, and I shouldn't need to explain why.

Ignore what the macro actually does, that's not the point.




Now, the way I most often see macros written is to enclose them in curly braces, like this:

#define MACRO(X,Y) \
{ \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl; \
}


This solves example 1, because the macro is in one statement block. But example 2 is broken because we put a semicolon after the call to the macro. This makes the compiler think the semicolon is a statement by itself, which means the else statement doesn't correspond to any if statement! And lastly, example 3 compiles OK, even though there is no semicolon, because a code block doesn't need a semicolon.




Is there a way to write a macro so that it pass all three examples?




Note: I am submitting my own answer as part of the accepted way of sharing a tip, but if anyone has a better solution feel free to post it here, it may get more votes than my method. :)

Answer

Macros should generally be avoided; prefer inline functions to them at all times. Any compiler worth its salt should be capable of inlining a small function as if it were a macro, and an inline function will respect namespaces and other scopes, as well as evaluating all the arguments once.

If it must be a macro, a while loop (already suggested) will work, or you can try the comma operator:

#define MACRO(X,Y) \
 ( \
  (cout << "1st arg is:" << (X) << endl), \
  (cout << "2nd arg is:" << (Y) << endl), \
  (cout << "3rd arg is:" << ((X) + (Y)) << endl), \
  (void)0 \
 )

The (void)0 causes the statement to evaluate to one of void type, and the use of commas rather than semicolons allows it to be used inside a statement, rather than only as a standalone. I would still recommend an inline function for a host of reasons, the least of which being scope and the fact that MACRO(a++, b++) will increment a and b twice.