How to make a C ++ macro behave like a function?

Let's say that for some reason you need to write a macro: MACRO(X,Y) . (Suppose there is a good reason why you cannot use the built-in function.) You want this macro to emulate a function call without a return value.




Example 1: This should work as expected.

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

Example 2: This should not lead to 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(); 



A naive way to record a macro is as follows:

 #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 decision, in which not all three examples, and I do not need to explain why.

Ignoring what the macro actually does is not the point.




Now, as I often see written macros, you need to enclose them in curly braces, for example:

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

This allows Example 1, because the macro is in the same statement block. But example 2 is broken, because we put a semicolon after calling the macro. This makes the compiler think that the semicolon is an operator in itself, which means that the else statement does not match any if statement! Finally, Example 3 compiles in order, although there is no semicolon because the code block does not need a semicolon.




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




Note. I am sending my own answer as part of the accepted way to share advice , but if someone has a better solution, feel free to post it here, it can get more votes than my method. :)

+42
c ++ c-preprocessor
02 Oct '08 at 16:41
source share
9 answers

Macros should be avoided; prefer built-in functions for them at any time. Any compiler deserving its salt should be able to nest a small function, as if it were a macro, and the built-in function will respect namespaces and other scopes, as well as evaluate all arguments once.

If this is a macro, the 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 \ ) 

(void)0 forces the operator to evaluate one of the void types, and the use of commas, not semicolons, allows it to be used inside the operator, and not just as stand-alone. I still recommend the built-in function for a number of reasons, the least of which is the scope and the fact that MACRO(a++, b++) will double a and b .

+37
02 Oct '08 at 16:54
source share

There is a pretty smart 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) 

You now have one block level statement followed by a semicolon. This behaves as expected and desirable in all three examples.

+39
02 Oct '08 at 16:41
source share

I know that you said β€œignore what the macro does,” but people will find this question by searching based on the name, so I think that discussing additional methods for emulating functions using macros is justified.

The closest I know:

 #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 these contexts.
  • Evaluates each of its arguments exactly once, which is a guaranteed function of the function call (assuming in both cases exceptions in any of these expressions).
  • Acts on any type using "auto" from C ++ 0x. This is not standard C ++ yet, but there is no other way to get the tmp variables required by the one-time rule.
  • It does not require the caller to import names from the std namespace that the original macro executes, but the function will not.

However, it still differs from the function in that:

  • For some inappropriate purposes, it may give various errors or warnings to the compiler.
  • It is incorrect if X or Y contains the use of "MACRO_tmp_1" or "MACRO_tmp_2" from the surrounding area.
  • Associated with the std namespace: a function uses its own lexical context to search for names, while a macro uses the context of its calling site. There is no way to write a macro that behaves as a function in this regard.
  • It cannot be used as a return expression of a void function, which can have a void expression (for example, a solution for a comma). This is even a big problem when the desired return type is not invalid, especially when using lvalue as the value. But a comma solution cannot include the use of declarations because they are operators, so choose one or use the GNU extension {...}).
+16
02 Oct '08 at 23:26
source share

Here is the answer coming from libc6 ! Looking at /usr/include/x86_64-linux-gnu/bits/byteswap.h , I found the trick you were looking for.

A few critics of previous decisions:

  • Kip's solution does not allow evaluating an expression, which is ultimately often necessary.
  • The coppro solution does not allow you to assign a variable, because the expressions are separate, but can be evaluated in the expression.
  • Steve Jessop's solution uses the C ++ 11 auto keyword, this is great, but feel free to use a known / expected type.

The trick is to use both the (expr,expr) construct and the {} region:

 #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, this is just a hint to the compiler. Macro parameters X and Y (already) surrounded in parentheses and casted for the expected type. This solution works correctly with pre-increment and post-increment, since the parameters are evaluated only once.

As an example, although not requested, I added the __x + __y; , which allows you to evaluate the entire block as an exact expression.

It is safer to use void(); if you want to make sure that the macro will not be evaluated in the expression, so it is illegal if rvalue is expected.

However , the solution does not meet the requirements of ISO C ++, as g++ -pedantic will complain:

 warning: ISO C++ forbids braced-groups within expressions [-pedantic] 

To give some g++ rest, 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; \ } \ )) 

To improve my solution even a little more, use the __typeof__ keyword, as shown 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 is also a gcc extension.

Pay attention to the removal of the register keyword, because when used with a class type, it will have the following warning:

 warning: address requested for '__x', which is declared 'register' [-Wextra] 
+11
Dec 21 2018-11-12T00:
source share

C ++ 11 brought us lambdas that 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 preserve the generating power of macros, but have a convenient area from which you can return whatever you want (including void ). In addition, the problem of evaluating macro parameters several times is eliminated.

+4
Nov 04 '16 at 13:26
source share

Create a block with

  #define MACRO(...) do { ... } while(false) 

Do not add a; after time (false)

+3
Oct 02 '08 at 16:50
source share

Your answer suffers from a multiple rating problem, therefore (for example)

 macro( read_int(file1), read_int(file2) ); 

will do something unexpected and possibly unwanted.

+2
02 Oct '08 at 17:11
source share

As already mentioned, you should avoid macros whenever possible. They are dangerous if there are side effects if the macro arguments are evaluated more than once. If you know the type of arguments (or you can use the C ++ 0x auto function), you can use temporary resources to force a single valuation check.

Another problem: the order in which multiple evaluations occur may not be what you expect!

Consider this code:

 #include <iostream> using namespace std; int foo( int & i ) { return i *= 10; } int bar( int & i ) { return i *= 100; } #define BADMACRO( X, Y ) do { \ cout << "X=" << (X) << ", Y=" << (Y) << ", X+Y=" << ((X)+(Y)) << endl; \ } while (0) #define MACRO( X, Y ) do { \ int x = X; int y = Y; \ cout << "X=" << x << ", Y=" << y << ", X+Y=" << ( x + y ) << endl; \ } while (0) int main() { int a = 1; int b = 1; BADMACRO( foo(a), bar(b) ); a = 1; b = 1; MACRO( foo(a), bar(b) ); return 0; } 

And it is output as compiled and running on my machine:

 X = 100, Y = 10000, X + Y = 110
 X = 10, Y = 100, X + Y = 110
0
Oct 02 '08 at 17:46
source share

If you are ready to take the practice of always using curly braces in your if statements,

Your macro will simply be missing the last semicolon:

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

Example 1: (compilation)

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

Example 2: (compilation)

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

Example 3: (does not compile)

 do_something(); MACRO(x, y) do_something(); 
-2
Oct 02 '08 at 16:54
source share



All Articles