Elliott Darfink
Elliott Darfink

Reputation: 1203

Pushing the C++ preprocessor

I'm trying to utilize the preprocessor in C++ in a way which would ease my development progress enormously! I have a pretty simple issue, I work with a C API library whilst I use regular C++ classes. This library is based on callbacks/events, which I only can supply functions (not methods) to. Because of this I have a repetitive pattern of declaring a static and non-static function for each event:

public: // Here is the static method which is required
    static inline Vector StaticEventClickLeft(Vector vec) { return globalClass->EventClickLeft(vec); }
private: // And here is the method (i.e non-static)
    Vector EventClickLeft(Vector vec);

I want to create a macro which defines both these in a single line. It would decrease the size of my header by ten times at least! Here is my closest attempt (but far from enough):

#define DECL_EVENT(func, ret, ...) \
    public: static inline ret S ## Event ## func(__VA_ARGS__) { return globalClass->Event ## func(__VA_ARGS__); } \
    private: ret Event ## func(__VA_ARGS__);

If I use this macro like this DECL_EVENT(ClickLeft, Vector, Vector vec). This will be the output:

public: static inline Vector SEventClickLeft(Vector vec) { return globalClass->EventClickLeft(Vector vec); }
private: Vector EventClickLeft(Vector vec);

You can clearly see the problem. The static function calls the method and supplies the type of the argument as well as the name. Since the type is specified, it results in a compiler error; include/plugin.h:95:2: error: expected primary-expression before ‘TOKEN’ token .

So how can I solve this? There must be some solution, and I'm sure some macro expert can offer some help!

Upvotes: 1

Views: 872

Answers (2)

Paul Fultz II
Paul Fultz II

Reputation: 18190

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

DECL_EVENT(ClickLeft, Vector, (Vector) vec)

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((Vector) vec) it will expand to vec. And when you write PAIR((Vector) vec) it will expand to Vector vec. 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 DECL_EVENT macro:

#define DECL_EVENT(func, ret, ...) \
public: static inline ret S ## Event ## func(APPLY(PAIR, __VA_ARGS__)) { return globalClass->Event ## func(APPLY(STRIP, __VA_ARGS__)); } \
private: ret Event ## func(APPLY(PAIR, __VA_ARGS__));

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

Upvotes: 1

Klemens Baum
Klemens Baum

Reputation: 561

Trying to abuse the preprocessor in C++ is usually a bad idea.

Is there any reason you can't use boost::signal for your needs?

As a bonus you could use std::function (or boost::function if you are stuck in C++03 land) to bind your member function to an object instance and get rid of the global.

Upvotes: 1

Related Questions