Macro function with several lines for the paramete

2020-04-02 06:31发布

问题:

In C++, I need to defined a macro. That macro would take as parameter a "block" of code.

Can we safely use several lines of code as parameter of a macro function?

I was asking myself if :

  1. is the following code valid, defined as valid by the standard, as in "cross-platform"?
  2. is there a better way to do the same (I can't use template function there because I need the context).

#define MY_MACRO( expr ) DOSOMETHING( (expr) ); DOANOTHERTHING( (expr) ); // etc...

int my_function() {
    int o = RandomNumber();
    MY_MACRO( 
        int k = AFunction();
        k++;
        AnotherFunction( k + o ); // here I need to keep the context of the call
    ); 
}

We can't use functors because we need to have access to the context of the call. We can't use lambda (snif) because we use an old compiler that don't provide it (and we can't change it).

回答1:

16.3/9:

Within the sequence of preprocessing tokens making up an invocation of a function-like macro, new-line is considered a normal white-space character.

So the multi-line macro invocation in general is fine. Of course if DOSOMETHING and DOANOTHERTHING don't introduce braces for scope, then your particular example will redefine k.

Edit:

We can't use functors because we need to have access to the context of the call. We can't use lambda (snif) because we use an old compiler

The usual way is to capture whichever variables you need in the functor, just like a lambda does. The only thing a lambda can do that a functor can't is "capture everything" without having to type it out, but whoever writes the lambda can see what variables they use, so that's just convenience, they could type them all out if they had to. In your example:

struct MyFunctor {
    int o;
    MyFunctor(int o) : o(o) {}
    void operator()() const {  // probably not void in practice
        int k = AFunction();
        k++;
        AnotherFunction( k + o );
    }
};

template<typename F>
void DoThings(const F &f) {
    DOSOMETHING(f());
    DOANOTHERTHING(f());
}

int my_function() {
    int o = RandomNumber();
    DoBothThings(MyFunctor(o));
}

You can also capture variables by reference (usually using a pointer as the data member rather than a reference, so that the functor can be copy-assigned).

If by "context", you mean for example that the macro argument and/or the macro body might contain a break or goto, and hence needs to be inside the lexical scope of the caller then you can't use a functor or a lambda. For shame ;-)



回答2:

the way to make it work (at least for gcc version 4.8.1 (Ubuntu/Linaro 4.8.1-10ubuntu9)), is to use braces { } enclosing your actuals for the macro.

A useful example:

#ifdef DEBUG
#define MYDEBUG(X) (X)
#else
#define MYDEBUG(X)
#endif

MYDEBUG({
  if (shit_happens) {
     cerr << "help!" << endl;
     ....
  }
});


回答3:

I think you will need to use extra parentheses to make your expressions look like a single argument which won't get broken up by the preprocessor, i.e. do it more like this:

#define MY_MACRO( (expr) ) DOSOMETHING( (expr) ); DOANOTHERTHING( (expr) ); // etc...

int my_function() {
    MY_MACRO( 
        (int k = AFunction();
        k++;
        AnotherFunction( k );)
    ); 
}

although I haven't actually tried it.



回答4:

In C++ you should use a functor! ;)

struct ninja
{
  void operator()() const
  {
    int k = AFunction();
    k++;
    AnotherFunction( k );    
  }
};

template <typename Functor>
void do_something(Functor const& f)
{
  f();
}

template <typename Functor>
void do_otherthing(Functor const& f)
{
  f();
}

int my_function()
{
  ninja foo;
  do_something(foo);
  do_otherthing(foo);
}


回答5:

The main problem that I see is that expr isn't an expression at all. It even contains a declaration. Obviously, you're going to have a problem with two variables k defined in my_function.

If you can use C++0x (e.g. VS2010, GCC4.6) then you can use lambda's to capture context. Not that you'd need to capture context for such a simple case, nor do you need templates, your macro just needs a std::function<void(void)>.



回答6:

A macro with multiple lines for parameter is fine also in case of multiple arguments, it even allows "commas" inside, but I strongly descourage using "commas" beause if it is not ambiguos to a machine it is certainly ambiguos to humans:

#include <iostream>
using namespace std;

#define MACRO2FUN( X, Y) x; y;
void function(int a, int b, int c){
    std::cout<<a<<" "<<b<<" "<<c<<std::endl;
}

int main() {
    MACRO2FUN(
      function(3,4,5), 
      function(6,7,8)
      )
      return 0;
}


标签: c++ macros