Tim Mahoney
Tim Mahoney

Reputation: 680

How to define a macro that defines a function that calls itself?

I want to make a macro that will define a function that calls that function on a list of objects. It doesn't necessarily have to be a preprocessor macro, but it should work.

I want to write something like this:

CALL_ON_ALL(doSomething(int arg1, bool arg2))

And I want it to produce this:

void doSomething(int arg1, bool arg2) {
    for (int i = 0; i < delegates.size(); i++)
        delegates[i]->doSomething(arg1, arg2);
}

I have something that works:

#define CALL_ON_ALL(defSig, callSig) \
void defSig { \
    for (int i = 0; i < delegates.size(); i++) \
        delegates[i]->callSig; \
}

The problem is that I have to write the definition signature and the call signature separately like this:

CALL_ON_ALL(doSomething(int arg1, bool arg2), doSomething(arg1, arg2))

Is there a better way to do this?

EDIT

It doesn't necessarily have to be a preprocessor macro. Anything that works will do.

Upvotes: 3

Views: 14661

Answers (5)

Paul Fultz II
Paul Fultz II

Reputation: 18210

First, place parenthesis around your types, so the preprocessor can parse it. So you will call CALL_ON_ALL like this:

CALL_ON_ALL(doSomething, (int) arg1, (bool) arg2)

Here are some macros that will retrieve the type and strip off the type(you will want to namespace them, I am leaving off the namespace just for demonstration):

#define EAT(...)
#define REM(...) __VA_ARGS__
#define STRIP(x) EAT x
#define PAIR(x) REM x

These macros work like this. When you write STRIP((int) arg1) it will expand to arg1. And when you write PAIR((int) arg1) it will expand to int arg1. Now next, you will want to do is to apply these macros to each argument that is passed in, so here is a simple APPLY macro that will let you do that for up to 8 arguments:

/* This counts the number of args */
#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define NARGS(...) NARGS_SEQ(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

/* This will let macros expand before concating them */
#define PRIMITIVE_CAT(x, y) x ## y
#define CAT(x, y) PRIMITIVE_CAT(x, y)

/* This will call a macro on each argument passed in */
#define APPLY(macro, ...) CAT(APPLY_, NARGS(__VA_ARGS__))(macro, __VA_ARGS__)
#define APPLY_1(m, x1) m(x1)
#define APPLY_2(m, x1, x2) m(x1), m(x2)
#define APPLY_3(m, x1, x2, x3) m(x1), m(x2), m(x3)
#define APPLY_4(m, x1, x2, x3, x4) m(x1), m(x2), m(x3), m(x4)
#define APPLY_5(m, x1, x2, x3, x4, x5) m(x1), m(x2), m(x3), m(x4), m(x5)
#define APPLY_6(m, x1, x2, x3, x4, x5, x6) m(x1), m(x2), m(x3), m(x4), m(x5), m(x6)
#define APPLY_7(m, x1, x2, x3, x4, x5, x6, x7) m(x1), m(x2), m(x3), m(x4), m(x5), m(x6), m(x7)
#define APPLY_8(m, x1, x2, x3, x4, x5, x6, x7, x8) m(x1), m(x2), m(x3), m(x4), m(x5), m(x6), m(x7), m(x8)

Now heres how you can write your CALL_ON_ALL macro:

#define CALL_ON_ALL(func, ...) \
void func(APPLY(PAIR, __VA_ARGS__)) { \
    for (int i = 0; i < delegates.size(); i++) \
        delegates[i]->func(APPLY(STRIP, __VA_ARGS__)); \
}

Note: This probably won't work in MSVC, since they have a buggy preprocessor(although there are workarounds).

Upvotes: 1

leemes
leemes

Reputation: 45675

This is a good example for higher order functions, that are functions taking another function as an argument. In this case, the function to be called on every element.

The following defines a higher order function calling f on every element. It requires C++11 for the variadic templates (args...). If you don't have C++11 available, you can remove the typename ...Args and work with fixed argument types in the function signature instead.

template<typename Delegates, typename Function, typename... Args>
void callOnAll(Delegates delegates, Function f, Args... args) {
    for (int i = 0; i < delegates.size(); i++)
        f(delegates[i], args...);
}

Now you can call this using this syntax:

callOnAll(delegates, std::mem_fun<void,Delegate>(&Delegate::doSomething), 42);

The std::mem_fun thing creates a temporary function object for the member function you want to call for each delegate. You can also apply other functions, taking a pointer as their first argument. For example this small lambda function (also only since C++11):

callOnAll(delegates, [](Delegate *d){
    d->doSomething(42);
});

which is pretty much the same, just another syntax.

See an example here:


A slightly different version of this code uses a range-based instead of indexed-based for-loop, which looks much cleaner (also requires C++11):

template<typename Delegates, typename Function, typename... Args>
void callOnAll(Delegates delegates, Function f, Args... args) {
    for(auto d : delegates)
        f(d, args...);
}

For your information, there is std::for_each, which does pretty much what you want, but in a slightly more functional way, as it doesn't take the function parameters by itself, but you provide a lambda function / functor which only takes a pointer to an instance. Compare the following code to the one above with the lambda function:

std::for_each(delegates.begin(), delegates.end(), [](Delegate *d){
    d->doSomething(42);
});

The only difference is that we have to pass .begin() and .end() iterators instead of just one single container instance, like delegates. However, there are a lot of other algorithms defined in the standard library which are worth looking at!

Upvotes: 5

Svetlin Mladenov
Svetlin Mladenov

Reputation: 4427

I think this is what you are looking for:

#define _NUM_ARGS2(X,X64,X63,X62,X61,X60,X59,X58,X57,X56,X55,X54,X53,X52,X51,X50,X49,X48,X47,X46,X45,X44,X43,X42,X41,X40,X39,X38,X37,X36,X35,X34,X33,X32,X31,X30,X29,X28,X27,X26,X25,X24,X23,X22,X21,X20,X19,X18,X17,X16,X15,X14,X13,X12,X11,X10,X9,X8,X7,X6,X5,X4,X3,X2,X1,N,...) N
#define NUM_ARGS(...) _NUM_ARGS2(0, ##__VA_ARGS__ ,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)

#define MAKE_PARAMS_0()
#define MAKE_PARAMS_1(type) type arg1
#define MAKE_PARAMS_2(type1, type2) type1 arg1, type2 arg2
#define MAKE_PARAMS_3(type1, type2, type3) type1 arg1, type2 arg2, type3 arg3
//.. add as many MAKE_PARAMS_* as you need

#define MAKE_PARAMS_N(N, ...) MAKE_PARAMS_##N(__VA_ARGS__)
#define MAKE_PARAMS_FORCE_N(N, ...) MAKE_PARAMS_N(N, __VA_ARGS__)
#define MAKE_PARAMS(...) MAKE_PARAMS_FORCE_N(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)


#define MAKE_ARGS_0()
#define MAKE_ARGS_1(type) arg1
#define MAKE_ARGS_2(t1, t2) arg1, arg2
#define MAKE_ARGS_3(t1, t2, t3) arg1, arg2, arg3
//.. add as many MAKE_ARGS_* as you have MAKE_PARAMS_*

#define MAKE_ARGS_N(N, ...) MAKE_ARGS_##N(__VA_ARGS__)
#define MAKE_ARGS_FORCE_N(N, ...) MAKE_ARGS_N(N, __VA_ARGS__)
#define MAKE_ARGS(...) MAKE_ARGS_FORCE_N(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define CALL_ON_ALL(fun, ...) \
    void fun(MAKE_PARAMS(__VA_ARGS__)) { \
        for (int i = 0; i < delegates.size(); i++) \
            delegates[i]->fun(MAKE_ARGS(__VA_ARGS__)); \
    }

CALL_ON_ALL(doSomething, int, bool)

this will generate

void doSomething(int arg1, bool arg2) { for (int i = 0; i < delegates.size(); i++) delegates[i]->doSomething(arg1, arg2); }

You can find more info how all this "mess" works here: Variadic recursive preprocessor macros - is it possible?

Upvotes: 3

Matthieu M.
Matthieu M.

Reputation: 299810

In C, there are variadic argument macros, which would be quite convenient here I suppose. They are not normally part of C++ though (subtle difference), though most compilers implementing them do not restrict them to C.

In C++ you are better off reaching toward templates, especially with variadic templates in C++11 (and perfect forwarding); however I will try to use the preprocessor, for fun and profit hum...

If we want a real C++03 preprocessor solution, then we shall call upon Boost.Preprocessor. The real kicker is the Preprocessor Sequence: an unbounded (in theory*) list of elements that can be manipulated at will, which gets us close enough to the convenience of variadic macros.

But before we delve in there, we should remark that the names of the arguments are quite unimportant, and that only their type really matters.

I thus propose the following syntax:

CALL_ON_ALL(dosomething, (int)(bool))

// (int)(bool) is a preprocessor sequence in Boost.Preprocessor

The innards are a bit more subtle, and I am not sure to get them right on first try, it should probably go something like:

#define CALL_ON_ALL(Name_, Args_)                                               \
    void Name_ ( BOOST_PP_ENUM( BOOST_PP_SEQ_SIZE(Args_), ARG_ENUM, Args_) ) {  \
        for (size_t i = 0, max = delegates.size(); i != max; ++i) {             \
            delegates[i]->                                                      \
                Name_ ( BOOST_PP_ENUM_PARAMS( BOOST_PP_SEQ_SIZE(Args_), arg );  \
        }                                                                       \
    }

#define ARG_ENUM(z, n, data)                                                    \
    BOOST_PP_SEQ_ELEM(data, n) BOOST_PP_CAT(arg, n)

Note: the complexity is not too terrific, there are N calls to BOOST_PP_SEQ_ELEM which is itself linear, leading to a quadratic complexity. I could not find a BOOST_PP_SEQ_* macro that could enumerate the arguments and place commas between them.

*In practice, there is a demo with close to a hundred elements in the docs, hope it suffice ;)

Upvotes: 1

Linuxios
Linuxios

Reputation: 35803

Try making the signature out of separate arguments (for two arguments):

#define CALL_ON_ALL2(name, argType1, argName1, argType2, argName2) \
void name( argType1 argName1 , argType2 argName2 ) { \
    for (int i = 0; i < delegates.size(); i++) \
        delegates[0]->name( argName1 , argName2 ); \
}

You can duplicate this for other argument numbers.

Upvotes: 6

Related Questions