2

I am attempting to use macros in C/C++ to generate some boiler-plate function declarations and definitions.

I would like a macro similar to:

DECLARE_FUNCTION(myFunction, int, A, int, B, char, C)

to generate the following code (please ignore the fact that this code seems pointless, it is just a simplified example)

void myFunction(int A, int B, char C) {
    myFunction_PROXY((Variant[4]){Variant(A), Variant(B), Variant(C), Variant()});
}
void myFunction_PROXY(const Variant (&args)[4]) {
    myFunction_HANDLER(args[0], args[1], args[2]);
}
void myFunction_HANDLER(int A, int B, char C) {

}

Notice how I need both the function signature (with types and names) as well as just the names to be used in the Variant() constructors. Thus, I would I assume I need to selectively "loop" through the VA_ARGS value of the macro to get different combinations of arguments and apply them in different ways.

Where I already have a class named Variant defined.

From my research, I believe this involves some combination of "recursive macros" however, I can't seem to understand how to get my desired output.

Would anyone be able to help or at least point me towards a good explanation on how recursive macros in C work?

Thanks

Patrick Wright
  • 1,401
  • 7
  • 13

5 Answers5

1

Disclaimer 1:

Honestly, I don't advise you do such machinery behind macros. They will become a massive maintenance burden, and the return is minimal. If you can do this with other more maintainable abstractions, it would be better for you in the long run.

Disclaimer 2:

I am writing this without a compiler at the moment. I may have a syntax error, but these are the generic building blocks to make this solution.


That disclaimer said, this can be done -- but not nicely.

You have several problems here that need to be tackled with a lot of macro trickery:

  • You want the expansion to contain the size of __VA_ARGS__ / 2 (at expansion time)
  • You want the expansion of __VA_ARGS__ to produce Variant(<arg 1>), Variant(<arg 3>), Variant(<arg 5>), Variant()
  • You want the expansion of __VA_ARGS__ to produce Variant[size]{args[0], args[1], args[2], ...}

To start with, I'm going to create a helper called JOIN:

#define JOIN(a, b) JOIN_H(a, b)
#define JOIN_H(a, b) a ## b

This may seem silly, but what it actually does is ensures that macros being joined together will be evaluated before they get joined -- so that macro functions being called will properly instantiate a join with their result rather than the full name.

Getting the size of __VA_ARGS__ / 2

Getting the size of __VA_ARGS__ usually requires two macros:

  • One that passes __VA_ARGS__, N, N-1, N-2, ... into a helper macro, and
  • Another that extracts that N at the end.

Something like:

#define COUNT_VA_ARGS(...) \
  COUNT_VA_ARGS_H(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define COUNT_VA_ARGS_H(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N,...) N

This works because the first one passes all the arguments of __VA_ARGS__ and counts backwards from the Nth number, and we then extract N.

In your case, you want the __VA_ARGS__ / 2, so you will need to double those arguments

#define COUNT_VA_ARGS(...) \
  COUNT_VA_ARGS_H(__VA_ARGS__, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0)
#define COUNT_VA_ARGS_H(_1, _1, _2, _2, _3, _3, _4, _4, _5, _5, _6, _6, _7, _7, _8, _8, _9, _9, _10, _10, N,...) N

Making __VA_ARGS__ produce Wrap(<arg 1>), Wrap(<arg 3>), ...

Unlike C++ variadic templates, macros are not able to have expansion expressions where you can wrap each argument. To simulate this in Macros, you pretty much have to have N expansions explicitly defined, and then to call this, you will need to combine the result of one macro to call it.

#define WRAP_VA_ARGS_0(wrap)
#define WRAP_VA_ARGS_1(wrap,x0) wrap(x0)
...
#define WRAP_VA_ARGS_10(wrap,x0,x1, ..., x10) wrap(x0), wrap(x1), ..., wrap(x10)

// Call into one of the concrete ones above
#define WRAP_VA_ARGS(wrap, __VA_ARGS__) JOIN(WRAP_VA_ARGS_, COUNT_VA_ARGS(__VA_ARGS__))(__VA_ARGS__)

Since the expression actually wants every other argument in it, you will again need to double the arguments:

#define WRAP_VA_ARGS_0(wrap)
#define WRAP_VA_ARGS_1(wrap,x0type,x0) wrap(x0)
#define WRAP_VA_ARGS_2(wrap,x0type,x0,x1type,x1) wrap(x0), wrap(x1)
...

Calling WRAP_VA_ARGS(Variant, int, A, float, B) will now create Variant(A), Variant(B)

Creating a list of index values

Similar to the above wrapping, you will need to find a way to generate a list of numbers, and wrap it. Again this must delegate to the counting wrapper

#define WRAP_COUNT_VA_ARGS_0(wrap)
#define WRAP_COUNT_VA_ARGS_1(wrap) wrap[0]
#define WRAP_COUNT_VA_ARGS_2(wrap) wrap[0], wrap[1]
...

#define WRAP_COUNT_VA_COUNT_ARGS(wrap, ...) JOIN(WRAP_COUNT_VA_ARGS_, COUNT_VA_ARGS(__VA_ARGS))(wrap)

Calling WRAP_COUNT_VA_COUNT_ARGS(args, int, A, float, B) should generate args[0], args[1]

Putting it all together

Trigger warning: this is going to be ugly

#define DECLARE_FUNCTION(name, ...) \  
void name(__VA_ARGS__) {            \
    JOIN(name, _PROXY)((Variant[COUNT_VA_ARGS(__VA_ARGS__)+1]) {WRAP_VA_ARGS(Variant,__VA_ARGS__), Variant()}); \
} \
void JOIN(name, _PROXY)(const Variant (&args)[COUNT_VA_ARGS(__VA_ARGS__) + 1]) { \
    JOIN(name, _HANDLER)(WRAP_COUNT_VA_COUNT_ARGS(args, __VA_ARGS__)); \
} \
void JOIN(name, _HANDLER)(__VA_ARGS__) { \
 \
} 

With any luck, an example of DECLARE_FUNCTION(myFunction, int, A, int, B, char, C) should produce:

void myFunction(int A, int B, char C) {
    myFunction_PROXY((Variant[3+1]{Variant(A), Variant(B), Variant(C), Variant()});
}
void myFunction_PROXY(const Variant (&args)[3+1]) { 
    myFunction_HANDLER(args[0], args[1], args[2]);
}
void myFunction_HANDLER(int A, int B, char C) {
    
}

Note: the array is created by the constant expression 3 + 1, since we need do this arithmetic to account for the Variant() at the end of myFunction_PROXY's call


Don't do macros. Macros are bad, mmmm'kay?

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
1

Iterating over comma-separated lists with preprocessor requires writing boilerplate macros.

Normally you must write or generate at least O(n) macros to process list up to n elements long. @Human-Compiler's answer does it with O(n2).

You can get similar macros from Boost.Preprocessor, or use it as an inspiration.

Or you can use a different syntax for your list:

DECLARE_FUNCTION(myFunction, (int,A)(int,B)(char,C))

Then you can process lists of any size, with a fixed amount of macros:

#define DECLARE_FUNCTION(func_, seq_) \
    void myFunction(END(PARAMS_LOOP_0 seq_)) { \
        myFunction_PROXY(
            (Variant[1 END(COUNT_LOOP_A seq_)]){END(VAR_LOOP_A seq_) Variant()}); \
    } \
    void myFunction_PROXY(const Variant (&args)[1 END(COUNT_LOOP_A seq_)]) { \
        const int x = __COUNTER__+1; \
        myFunction_HANDLER(END(ARR_LOOP_0 seq_)); \
    } \
    void myFunction_HANDLER(END(PARAMS_LOOP_0 seq_)) {}
    
#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END
    
#define PARAMS_LOOP_0(type_, name_)   PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_A(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_B
#define PARAMS_LOOP_B(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_0_END
#define PARAMS_LOOP_A_END
#define PARAMS_LOOP_B_END
#define PARAMS_LOOP_BODY(type_, name_) type_ name_

#define COUNT_LOOP_A(...) COUNT_LOOP_BODY COUNT_LOOP_B
#define COUNT_LOOP_B(...) COUNT_LOOP_BODY COUNT_LOOP_A
#define COUNT_LOOP_A_END
#define COUNT_LOOP_B_END
#define COUNT_LOOP_BODY +1

#define VAR_LOOP_A(type_, name_) VAR_LOOP_BODY(name_) VAR_LOOP_B
#define VAR_LOOP_B(type_, name_) VAR_LOOP_BODY(name_) VAR_LOOP_A
#define VAR_LOOP_A_END
#define VAR_LOOP_B_END
#define VAR_LOOP_BODY(name_) Variant(name_), 

#define ARR_LOOP_0(...)   ARR_LOOP_BODY ARR_LOOP_A
#define ARR_LOOP_A(...) , ARR_LOOP_BODY ARR_LOOP_B
#define ARR_LOOP_B(...) , ARR_LOOP_BODY ARR_LOOP_A
#define ARR_LOOP_A_END
#define ARR_LOOP_B_END
#define ARR_LOOP_BODY args[__COUNTER__-x]

With those macros, DECLARE_FUNCTION(myFunction, (int,A)(int,B)(char,C)) expands to:

void myFunction(int A, int B, char C)
{
    myFunction_PROXY((Variant[1+1+1+1]){Variant(A), Variant(B), Variant(C), Variant()});
}

void myFunction_PROXY(const Variant (&args)[1+1+1+1])
{
    const int x = 0+1;
    myFunction_HANDLER(args[1-x], args[2-x], args[3-x]);
}

void myFunction_HANDLER(int A, int B, char C) {}

Note the use __COUNTER__. It's not a part of the standard C++, but the major compilers support it as an extension. You don't have any other options for getting consecutive array indices, other than writing boilerplate macros.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
1

I've found extremely useful all your reply; in my case I had the need to protect a set of routines implemented as part of an old application.

Constraints: mutex, minimize changes in the code.

The "MTX_DB_PROTECTED_FUNCTION" macro works great.

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END

#define PARAMS_LOOP_0(type_, name_)   PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_A(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_B
#define PARAMS_LOOP_B(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_0_END
#define PARAMS_LOOP_A_END
#define PARAMS_LOOP_B_END
#define PARAMS_LOOP_BODY(type_, name_) type_ name_

#define VAR_LOOP_0(type_, name_)   VAR_LOOP_BODY(type_, name_) VAR_LOOP_A
#define VAR_LOOP_A(type_, name_) , VAR_LOOP_BODY(type_, name_) VAR_LOOP_B
#define VAR_LOOP_B(type_, name_) , VAR_LOOP_BODY(type_, name_) VAR_LOOP_A
#define VAR_LOOP_0_END
#define VAR_LOOP_A_END
#define VAR_LOOP_B_END
#define VAR_LOOP_BODY(type_, name_) name_

//https://stackoverflow.com/questions/62903631/use-c-c-macros-to-generate-function-signature
#define MTX_DB_PROTECTED_FUNCTION(type_, func_, seq_) \
\
static type_ _s_mtx_##func_##_protected(END(PARAMS_LOOP_0 seq_));\
\
type_ func_(END(PARAMS_LOOP_0 seq_))\
{\
    UTL_AcquireMutex(__FUNCTION__, &g_h_dataFileMutex, OSL_TIMEOUT_INFINITE);\
    type_ ret = _s_mtx_##func_##_protected(END(VAR_LOOP_0 seq_));\
    UTL_ReleaseMutex(__FUNCTION__, &g_h_dataFileMutex);\
    return ret;\
}\
\
\
static type_ _s_mtx_##func_##_protected(END(PARAMS_LOOP_0 seq_))

Sample

Original func

int dummyfunc(char TabId, char checksum)
{
    return 0;
}

Macro insertion

MTX_DB_PROTECTED_FUNCTION(int, dummyfunc, (char,TabId)(char,checksum))
{
    return 0;
}

Macro expansion

static int  _s_mtx_dummyfunc_protected(char  TabId , char  checksum );        
                                                                              
int  dummyfunc(char  TabId , char  checksum )                                 
{                                                                             
    UTL_AcquireMutex(__FUNCTION__, &g_h_dataFileMutex, (unsigned long)(-1));  
    int ret = _s_mtx_dummyfunc_protected(TabId , checksum );                  
    UTL_ReleaseMutex(__FUNCTION__, &g_h_dataFileMutex);                       
    return ret;                                                               
}                                                                             
                                                                              
static int  _s_mtx_dummyfunc_protected(char  TabId , char  checksum )
{
    return 0;
}

For no-params function

MTX_DB_PROTECTED_FUNCTION(int, dummyWoParams,(,))
{
galgoog
  • 11
  • 2
0

Based upon many of the other answers and comments, here is what I cam up with that solves my particular problem:

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END

#define PARAMS_LOOP_0(type_, name_)   PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_A(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_B
#define PARAMS_LOOP_B(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_0_END
#define PARAMS_LOOP_A_END
#define PARAMS_LOOP_B_END
#define PARAMS_LOOP_BODY(type_, name_) type_ name_

#define VAR_LOOP_0(type_, name_) , VAR_LOOP_BODY(type_, name_) VAR_LOOP_A
#define VAR_LOOP_A(type_, name_) , VAR_LOOP_BODY(type_, name_) VAR_LOOP_B
#define VAR_LOOP_B(type_, name_) , VAR_LOOP_BODY(type_, name_) VAR_LOOP_A
#define VAR_LOOP_0_END
#define VAR_LOOP_A_END
#define VAR_LOOP_B_END
#define VAR_LOOP_BODY(type_, name_) name_

//------------------------------------------------------------------------------

#define DEFINE_SLOT(func_, ...) \
    void func_(END(PARAMS_LOOP_0 __VA_ARGS__)) { invoke(this, &func_##_SLOT END(VAR_LOOP_0 __VA_ARGS__)); }\
    void func_##_SLOT(END(PARAMS_LOOP_0 __VA_ARGS__))

#define DECLARE_SLOT(func_, ...) \
    void func_(END(PARAMS_LOOP_0 __VA_ARGS__));\
    void func_##_SLOT(END(PARAMS_LOOP_0 __VA_ARGS__))

#define DECLARE_VIRTUAL_SLOT(func_, ...) \
    virtual void func_(END(PARAMS_LOOP_0 __VA_ARGS__));\
    virtual void func_##_SLOT(END(PARAMS_LOOP_0 __VA_ARGS__))

#define DECLARE_SLOT_INTERFACE(func_, ...) \
    virtual void func_(END(PARAMS_LOOP_0 __VA_ARGS__)) = 0;\
    virtual void func_##_SLOT(END(PARAMS_LOOP_0 __VA_ARGS__)) = 0

#define DECLARE_SLOT_OVERRIDE(func_, ...) \
    void func_(END(PARAMS_LOOP_0 __VA_ARGS__)) override;\
    void func_##_SLOT(END(PARAMS_LOOP_0 __VA_ARGS__)) override

I have adopted some of Qt's naming conventions by calling these "slots."

These macros would be used as follows:

//In the .h file
class MyBase {
public:
  DECLARE_SLOT_INTERFACE(foo, (int, a) (int, b));
};

class MyClass : public MyBase {
public:
  DECLARE_SLOT_OVERRIDE(foo, (int, a) (int, b));
  DECLARE_SLOT(bar, (bool, a) (const MyClass& obj));
};

//In the .cpp file
DEFINE_SLOT(MyClass::foo, (int, a) (int, b)) {

}

DEFINE_SLOT(MyClass::bar, (bool, a) (const MyClass& obj)) {

}
Patrick Wright
  • 1,401
  • 7
  • 13
-1

Not very clear what it variable into your case, you can do what you are looking for with __VA_ARGS__

#define DECLARE_FUNCTION(myFunction, ...)                                     \
static void myFunction##_HANDLER(__VA_ARGS__) {                               \
                                                                              \
}                                                                             \
\
static void myFunction##_PROXY(const Variant (&args)[4]) {                    \
    myFunction##_HANDLER(args[0], args[1], args[2]);                          \
}                                                                             \
\
static void  myFunction(__VA_ARGS__) {                                        \
    myFunction##_PROXY((Variant[4]){Variant(A), Variant(B), Variant(C), Variant()}); \
}

DECLARE_FUNCTION(myFunction, int A, int B, char C)

Will generate


static void myFunction_HANDLER(int A, int B, char C) { } 
static void myFunction_PROXY(const Variant (&args)[4]) { 
    myFunction_HANDLER(args[0], args[1], args[2]);
}
static void myFunction(int A, int B, char C) {
 myFunction_PROXY((Variant[4]){Variant(A), Variant(B), Variant(C), Variant()});
}
Ôrel
  • 7,044
  • 3
  • 27
  • 46
  • 1
    The problem here is that the macro code for myFunction has Variant(A), Variant(B), ... hardcoded into the call to the PROXY function. A, B, ... are the names of the variables passed into the macro by the user so those have to be generated as well. – Patrick Wright Jul 14 '20 at 21:01
  • number of args and type can be different ? – Ôrel Jul 14 '20 at 21:11
  • I see you change it from C to C++, for me it is very strange do have this kind of need in C++ to be done via MACRO. You can use FOR_EACH like done here: https://stackoverflow.com/a/11994395 – Ôrel Jul 14 '20 at 21:18