2

Is there a way to extract every second parameter from a list of variadic parameters in C/C++ preprocessor?

I would like to write a macro to generate boilerplate code for interface methods in the following way:

#define INTERFACE_FN(_NAME, _X, _Y, _N_PARAMS, ...) \
    void _NAME(__VA_ARGS__) \
    { \
        processing_function(_X, _Y, _N_PARAMS, EVERY_SECOND(__VA_ARGS__); \
    }

void processing_function(int x, int y, int count, ...)
{
    va_list params;
    va_start(params, count);
    while (count--) {
        do_something(va_arg(params, ParentClass*));
    }
    va_end(params);
    // do something else
}

Which would be used as:

INTERFACE_FN(foo, 1, 2, 0);
INTERFACE_FN(bar, 3, 4, 1, ClassX*, x);
INTERFACE_FN(jar, 5, 6, 2, ClassY*, y, ClassZ*, z);

The classes ClassX, ClassY, ClassZ are derivatives of ParentClass;

I'm looking for the EVERY_SECOND macro.

Brain
  • 311
  • 2
  • 12
  • 1
    That would expand to `void jar(ClassY *y, ClassZ *z) { processing_function(5, 6, 2, ClassZ *z); }`, which is a syntax error. – melpomene Aug 18 '17 at 12:44
  • @melpomene I think the idea is that the macro would actually be called as ex. `INTERFACE_FN(bar, 3, 4, 1, ClassX, *x);` and that `EVERY_SECOND` basically grabs the named parameter instead of the type name. – dbush Aug 18 '17 at 12:47
  • @dbush That would expand to `void bar(ClassX, *x) { processing_function(3, 4, 1, *x); }`, which is a syntax error. – melpomene Aug 18 '17 at 12:49
  • @melpomene Sorry, there was a mistake in the INTERFACE_FN usage. Now it is corrected. The type is one parameter and the variable name the next one, and so on. – Brain Aug 18 '17 at 12:50
  • @Brain That would expand to `void jar(ClassY*, y, ClassZ*, z) { processing_function(5, 6, 2, y, z); }`, which is a syntax error. – melpomene Aug 18 '17 at 12:50
  • @melpomene Ah, correct :-) But you got what I'm trying to do. Is there a way how to do this? – Brain Aug 18 '17 at 12:53
  • Why use varargs functions and macros at all? Why not variadic templates? – melpomene Aug 18 '17 at 12:53
  • @melpomene That might be an option. But is it possible to make them type safe? – Brain Aug 18 '17 at 13:01
  • Variadic templates are type safe (unlike varargs functions). – melpomene Aug 18 '17 at 13:06
  • I need to declare the interface functions really as functions - in the end they will be class methods. So I need to end up with foo(), bar(x), jar(y, z) and so on. Not with whatever(), whatever(x), whatever(y, z). How do I do this with variadic templates? Could you maybe give a small example please? – Brain Aug 18 '17 at 13:32
  • From a _macro_ perspective, it appears the reason you're wanting an "every second" is because your parameters are planned to be `[type, var [,type, var [...]]]`; the types and vars are associated; and you need them separately. Though an "every other" macro is _possible_ (as the existing answer demonstrates; there are also more elegant ways to do this), it may be _better_ to associate these in the call for this precise reason; e.g., `INTERFACE_FN(bar, 3, 4, 1, (ClassX*, x))` and `INTERFACE_FN(jar, 5, 6, 2, (ClassY*, y), (ClassZ*, z))` – H Walters Aug 18 '17 at 15:33

2 Answers2

2

Here is a working example of what you asked for

#define EVERY_SECOND0(...)

#define EVERY_SECOND1_(second, ...) , second
#define EVERY_SECOND1(first, ...) EVERY_SECOND1_(__VA_ARGS__)

#define EVERY_SECOND2_(second, ...) , second EVERY_SECOND1(__VA_ARGS__)
#define EVERY_SECOND2(first, ...) EVERY_SECOND2_(__VA_ARGS__)

#define EVERY_SECOND3_(second, ...) , second EVERY_SECOND2(__VA_ARGS__)
#define EVERY_SECOND3(first, ...) EVERY_SECOND3_(__VA_ARGS__)

#define EVERY_SECOND4_(second, ...) , second EVERY_SECOND3(__VA_ARGS__)
#define EVERY_SECOND4(first, ...) EVERY_SECOND4_(__VA_ARGS__)

#define EVERY_SECOND5_(second, ...) , second EVERY_SECOND4(__VA_ARGS__)
#define EVERY_SECOND5(first, ...) EVERY_SECOND5_(__VA_ARGS__)

#define COUNT_EVERY_SECOND(_1,__1,_2,__2,_3,__3,_4,__4,_5,__5,num,...) EVERY_SECOND ## num
#define EVERY_SECOND(...) COUNT_EVERY_SECOND(__VA_ARGS__,5,5,4,4,3,3,2,2,1,0)(__VA_ARGS__)


#define INTERFACE_FN(_NAME, _X, _Y, _N_PARAMS, ...) \
    void _NAME(__VA_ARGS__) \
    { \
        processing_function(_X, _Y, _N_PARAMS EVERY_SECOND(__VA_ARGS__)); \
    }

class parentClass {};
class ClassX {};
class ClassY {};
class ClassZ {};

void processing_function(int x, int y, int count, ...)
{
    va_list params;
    va_start(params, count);
    while (count--) {
        do_something(va_arg(params, ParentClass*));
    }
    va_end(params);
    // do something else
}

INTERFACE_FN(foo, 1, 2, 0);
INTERFACE_FN(bar, 3, 4, 1, ClassX*, x);
INTERFACE_FN(jar, 5, 6, 2, ClassY*, y, ClassZ*, z);

The preprocessor output looks like this

class parentClass {};
class ClassX {};
class ClassY {};
class ClassZ {};

void processing_function(int x, int y, int count, ...)
{
 va_list params;
 va_start(params, count);
 while (count--) {
  do_something(va_arg(params, ParentClass*));
 }
 va_end(params);

}

void foo() { processing_function(1, 2, 0 ); };
void bar(ClassX*, x) { processing_function(3, 4, 1 , x); };
void jar(ClassY*, y, ClassZ*, z) { processing_function(5, 6, 2 , y , z); };

Though I will give the obligatory warning that there is probably an easier way to do this without macros.

This will have an extra comma at the end if you put an odd number of arguments, though that shouldn't happen for your use case. This allows up to 5 pairs (10 total arguments) but I think you can see the pattern for how to add support for more. There is no way to do this without explicitly defining for each number of args due to the preprocessors limitations

Have fun!

Edit:

I would also like to note that this can be improved so that you never have to specify _N_PARAMS and let the macros count for you, just add a macro

#define COUNT_EVERY_SECOND_(_1,__1,_2,__2,_3,__3,_4,__4,_5,__5,num,...) num

and replace INTERFACE_FNs definition with this

#define INTERFACE_FN(_NAME, _X, _Y, ...) \
    void _NAME(__VA_ARGS__) \
    { \
        processing_function(_X, _Y, COUNT_EVERY_SECOND_(__VA_ARGS__,5,ERROR,4,4,3,3,2,2,1,0) EVERY_SECOND(__VA_ARGS__)); \
    }
Community
  • 1
  • 1
rtpax
  • 1,687
  • 1
  • 18
  • 32
0

If you use c++20, then you can do something like this:

#include <iostream>

// Magic starts
// https://www.scs.stanford.edu/~dm/blog/va-opt.html
#define PARENS ()

#define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
#define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) __VA_ARGS__

#define FOR_EACH(macro, ...)                                    \
  __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, a1, a2, ...)                     \
  macro(a1, a2)                                                 \
  __VA_OPT__(, FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
#define FOR_EACH_AGAIN() FOR_EACH_HELPER
// Magic ends

#define EXTRACT_SECOND(x, y) y

#define EXTRACT_EACH_SECOND(...) FOR_EACH(EXTRACT_SECOND, __VA_ARGS__)


int main() {
    // Sorry for non-interesting use case
    int array[] = { EXTRACT_EACH_SECOND(1, 2, 3, 4, 5, 6) };

    for (const auto& elem: array) {
       std::cout << elem << std::endl; // prints 2, 4, 6
    }
}