0
votes

I have a lot of lines of code like below:

     sp_setup_point( setup, get_vert(vertex_buffer, i-0, stride) );

for each of them, I want to be able to extract (i-0) and pass it to another function. like:

     sp_setup_point( setup, get_vert(vertex_buffer, i-0, stride) );
     my_test_func(i-0);

so I wrote two macro:

      #define GET_VERT(_x, _y, _z) get_vert(_x, _y, _z) , _y
      #define SP_SETUP_POINT(_x, _y, _z) sp_setup_point(_x, _y); my_test_func(_z);

and call them like:

     SP_SETUP_POINT( setup, GET_VERT(vertex_buffer, i-0, stride));

however, it does not give what I want, it expands to:

     sp_setup_point(setup, get_vert(vertex_buffer, i-0, stride), i-0); my_test_func();

and MSVC compiler complains

    not enough actual parameters for macro 'SP_SETUP_POINT'

I searched quite a bit, according to https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html

Macro arguments are completely macro-expanded before they are substituted into a macro body, unless they are stringified or pasted with other tokens. After substitution, the entire macro body, including the substituted arguments, is scanned again for macros to be expanded. The result is that the arguments are scanned twice to expand macro calls in them

the argument are fully expanded, but the additional argument is not recognized. how it that ? any suggestion is appreciated.

2
Hint: Use your compiler's option to emit the preprocessed sourcecode to see what the expression evaluates to.doynax
Yes, I tried. I got the code after preprocessed, but still dont know how to get desired macro expansion.luckyyang

2 Answers

1
votes

The reason, IIRC, is that GET_VERT inside of an actual parameter list gets only expanded after the enclosing macro call is scanned. So the PP first wants to see the ")" at the end of the parameter list of SP_SETUP_POINT. In this process it recognizes that it is one parameter short of the declared form. One level of indirection helps, but beware, macro programming in C is strange and suffers from early bad implementation choices that no one ever dared to correct due to massive compatibility debt.

#define GET_VERT(_x, _y, _z) get_vert(_x, _y, _z) , _y
#define SP_SETUP_POINT(_x, expandme) SP_SETUP_POINT_(_x,expandme)
#define SP_SETUP_POINT_(_x,_y,_z) sp_setup_point(_x, _y); my_test_func(_z)

The last form is better written as

#define SP_SETUP_POINT_(_x,_y,_z) do{ sp_setup_point(_x, _y); my_test_func(_z); }while(0)

because writing sg. like

if (a==1) SP_SETUP_POINT(setup, GET_VERT(vertex_buffer, i-0, stride));

will lead to invisible errors in the control flow.

1
votes

If I was going to tackle this, I think I'd start from the desired output:

 sp_setup_point( setup, get_vert(vertex_buffer, i-0, stride) );
 my_test_func(i-0);

You have 4 parameters: setup, vertex_buffer, expr, and stride, where expr is the i-0 parameter (which is itself a peculiar notation; how does that differ from i?). I might use those longer names in production-quality macros; I'm going to use short names a .. d here.

So, I'd design my macro from that starting point:

#define SP_SETUP_POINT(a, b, c, d) \
    do { sp_setup_point((a), get_vert((b), (c), (d))); \
         my_test_func(c); } while (0)

You could then invoke:

SP_SETUP_POINT(setup, vertex_buffer, i-0, stride);

This would generate the code you desire, even in a context such as:

if (x > y)
    SP_SETUP_POINT(setup, vertex_buffer, i-0, stride);
else
    SP_SETUP_POINT(setup, vertex_buffer, i+2, stride);

If you don't use the do { … } while (0) notation, then you have to use a comma operator to separate the function calls:

#define SP_SETUP_POINT(a, b, c, d) \
    (sp_setup_point((a), get_vert((b), (c), (d))), \
     my_test_func(c))

This evaluates to the return value from my_test_func(). There's no problem if you don't need to test the return value from sp_setup_point() — e.g. if it returns void anyway — and there's no problem if my_test_func() returns void.

You can also modify the macro to call MY_TEST_FUNC(c) and then conditionally define it:

#ifdef CALL_MY_TEST_FUNCTION
#define MY_TEST_FUNC(c) my_test_func(c)
#else
#define MY_TEST_FUNC(c) ((void)(c))
#endif

The advantage of evaluating c in the 'do not call' case is that the compiler ensures that c remains valid even as the code changes. Do not under-estimate the benefit of that in long-lived code.