How do I make a C++ macro behave like a function?
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.
There is a rather clever solution:
#define MACRO(X,Y) \
do { \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl; \
} while (0)
Now you have a single block-level statement, which must be followed by a semicolon. This behaves as expected and desired in all three examples.
I know you said "ignore what the macro does", but people will find this question by searching based on the title, so I think discussion of further techniques to emulate functions with macros are warranted.
Closest I know of is:
#define MACRO(X,Y) \
do { \
auto MACRO_tmp_1 = (X); \
auto MACRO_tmp_2 = (Y); \
using std::cout; \
using std::endl; \
cout << "1st arg is:" << (MACRO_tmp_1) << endl; \
cout << "2nd arg is:" << (MACRO_tmp_2) << endl; \
cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; \
} while(0)
This does the following:
- Works correctly in each of the stated contexts.
- Evaluates each of its arguments exactly once, which is a guaranteed feature of a function call (assuming in both cases no exceptions in any of those expressions).
- Acts on any types, by use of "auto" from C++0x. This is not yet standard C++, but there's no other way to get the tmp variables necessitated by the single-evaluation rule.
- Doesn't require the caller to have imported names from namespace std, which the original macro does, but a function would not.
However, it still differs from a function in that:
- In some invalid uses it may give different compiler errors or warnings.
- It goes wrong if X or Y contain uses of 'MACRO_tmp_1' or 'MACRO_tmp_2' from the surrounding scope.
- Related to the namespace std thing: a function uses its own lexical context to look up names, whereas a macro uses the context of its call site. There's no way to write a macro that behaves like a function in this respect.
- It can't be used as the return expression of a void function, which a void expression (such as the comma solution) can. This is even more of an issue when the desired return type is not void, especially when used as an lvalue. But the comma solution can't include using declarations, because they're statements, so pick one or use the ({ ... }) GNU extension.
Here is an answer coming right from the libc6
!
Taking a look at /usr/include/x86_64-linux-gnu/bits/byteswap.h
, I found the trick you were looking for.
A few critics of previous solutions:
- Kip's solution does not permit evaluating to an expression, which is in the end often needed.
- coppro's solution does not permit assigning a variable as the expressions are separate, but can evaluate to an expression.
- Steve Jessop's solution uses the C++11
auto
keyword, that's fine, but feel free to use the known/expected type instead.
The trick is to use both the (expr,expr)
construct and a {}
scope:
#define MACRO(X,Y) \
( \
{ \
register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
std::cout << "1st arg is:" << __x << std::endl; \
std::cout << "2nd arg is:" << __y << std::endl; \
std::cout << "Sum is:" << (__x + __y) << std::endl; \
__x + __y; \
} \
)
Note the use of the register
keyword, it's only a hint to the compiler.
The X
and Y
macro parameters are (already) surrounded in parenthesis and casted to an expected type.
This solution works properly with pre- and post-increment as parameters are evaluated only once.
For the example purpose, even though not requested, I added the __x + __y;
statement, which is the way to make the whole bloc to be evaluated as that precise expression.
It's safer to use void();
if you want to make sure the macro won't evaluate to an expression, thus being illegal where an rvalue
is expected.
However, the solution is not ISO C++ compliant as will complain g++ -pedantic
:
warning: ISO C++ forbids braced-groups within expressions [-pedantic]
In order to give some rest to g++
, use (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE)
so that the new definition reads:
#define MACRO(X,Y) \
(__extension__ ( \
{ \
register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
std::cout << "1st arg is:" << __x << std::endl; \
std::cout << "2nd arg is:" << __y << std::endl; \
std::cout << "Sum is:" << (__x + __y) << std::endl; \
__x + __y; \
} \
))
In order to improve my solution even a bit more, let's use the __typeof__
keyword, as seen in MIN and MAX in C:
#define MACRO(X,Y) \
(__extension__ ( \
{ \
__typeof__(X) __x = (X); \
__typeof__(Y) __y = (Y); \
std::cout << "1st arg is:" << __x << std::endl; \
std::cout << "2nd arg is:" << __y << std::endl; \
std::cout << "Sum is:" << (__x + __y) << std::endl; \
__x + __y; \
} \
))
Now the compiler will determine the appropriate type. This too is a gcc
extension.
Note the removal of the register
keyword, as it would the following warning when used with a class type:
warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]
C++11 brought us lambdas, which can be incredibly useful in this situation:
#define MACRO(X,Y) \
[&](x_, y_) { \
cout << "1st arg is:" << x_ << endl; \
cout << "2nd arg is:" << y_ << endl; \
cout << "Sum is:" << (x_ + y_) << endl; \
}((X), (Y))
You keep the generative power of macros, but have a comfy scope from which you can return whatever you want (including void
). Additionally, the issue of evaluating macro parameters multiple times is avoided.