Using variadic macros or templates to implement a

2019-01-25 18:31发布

问题:

I have a set of methods used to instanciate and initialize a set of objects. They all look pretty much the same, except for the number of arguments that are passed to the Init function :

ObjectType* CreateObjectType(Arg1 a1, Arg2 arg2, ... ArgN aN)
{
    ObjectType* object = new ObjectType();
    [...]
    object->Init(this, a1, a2, ..., aN);
    [...]
    return object;
}

Note that the arguments are not to be used anywhere except to be passed to the Init function.

I would like to find a way to implement all of those without having to duplicate the code for each object type.


I tried using variadic macros, with the following (invalid) result :

#define CREATE_OBJECT_IMPL(ObjectType, ...)    \
ObjectType* Create##ObjectType##(__VA_ARGS__)  \
{                                              \
    ObjectType* object = new ObjectType();     \
    [...]
    object->Init(this, ##__VA_ARGS__);         \
    [...]
    return object;                             \
}

// This is the result I am trying to achieve :
CREATE_OBJECT_IMPL(MyFirstObject, bool, float)
CREATE_OBJECT_IMPL(MySecondObject, int)
CREATE_OBJECT_IMPL(MyThirdObject)

Now, in this implementation, I used VA_ARGS twice, both times incorrectly :

  • In the first case, I want to have a list of arguments with the types I specified (Arg1 a1, Arg2 a2...)

  • In the second case, I want to call these arguments by their names ( Init(a1, a2...) ).


I tried using variadic templates :

template< typename ObjectType, typename... Args >
ObjectType* CreateObject(Args args)
{
    ObjectType* object = new ObjectType();
    [...]
    object->Init(this, args);
    [...]
    return object;
}

#define CREATE_OBJECT_IMPL(ObjectType, ...)                     \
ObjectType* Create##ObjectType##(__VA_ARGS__)                   \
{                                                               \
    return CreateObject<ObjectType, __VA_ARGS__>(__VA_ARGS__);  \
}

...but this doesn't seem to work as well, I get the following error on the template definition line :

error C2143: syntax error : missing ',' before '...'

error C2065: 'Args' : undeclared identifier

I am using VS2012.

I could still write N similar macros for each number of arguments, however I was wondering if there was a way to obtain the same result without duplicating code?

回答1:

There are a couple of ways to solve the problem. First, you can use typed expression in the macro so you can parse the type. So the CREATE_OBJECT_IMPL would be called like this:

CREATE_OBJECT_IMPL(Object, (Arg1) arg1, (Arg2) arg2)

Here are some macros that will retrieve the type and strip off the type:

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

These macros work like this. When you write STRIP((Arg1) arg1) it will expand to arg1. And when you write PAIR((Arg1) arg1) it will expand to Arg1 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)

Then you can define the CREATE_OBJECT_IMPL like this:

#define CREATE_OBJECT_IMPL(ObjectType, ...) \
ObjectType* Create##ObjectType(APPLY(PAIR, __VA_ARGS__))  \
{ \
    ObjectType* object = new ObjectType(); \
    [...] \
    object->Init(this, APPLY(STRIP, __VA_ARGS__)); \
    [...] \
    return object; \
}

Of course, you might need some workarounds for these macros, if you use them on visual studio. Of course, a better solution is to write a templated function. So you would call your CreateObject like this:

ObjectType* obj = CreateObject<ObjectType>(arg1, arg2, arg3);

In C++11, you can use varidiac templates like this:

template< typename ObjectType, typename... Args >
ObjectType* CreateObject(Args... args)
{
    ObjectType* object = new ObjectType();
    [...]
    object->Init(this, args...);
    [...]
    return object;
}

But if your compiler doesn't support varidiac templates, you can use the Boost.PP to generate overloads for up to 10 arguments(or more if you need to):

#define GENERATE_OBJS_EACH(z, n, data) \
template<class ObjectType, BOOST_PP_ENUM_PARAMS_Z(z, n, class Arg)> \
ObjectType* CreateObject(BOOST_PP_ENUM_BINARY_PARAMS_Z(z, n, Arg, arg))  \
{ \
    ObjectType* object = new ObjectType(); \
    [...] \
    object->Init(this, BOOST_PP_ENUM_PARAMS_Z(z, n, arg)); \
    [...] \
    return object; \
}
/* Generate CreateObject template for up to 10 arguments */
BOOST_PP_REPEAT_FROM_TO_1(1, 10, GENERATE_OBJS_EACH, ~)

Edit: Heres are the workarounds you would need to get the above macros to work in msvc:

/* This counts the number of args */
#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define NARGS_MSVC_WORKAROUND(x) NARGS_SEQ x
#define NARGS(...) NARGS_MSVC_WORKAROUND((__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_MSVC_WORKAROUND(x) PRIMITIVE_CAT x
#define CAT(x, y) CAT_MSVC_WORKAROUND((x, y))

/* This will call a macro on each argument passed in */
#define APPLY(macro, ...) APPLY_MSVC_WORKAROUND(CAT(APPLY_, NARGS(__VA_ARGS__)), (macro, __VA_ARGS__))
#define APPLY_MSVC_WORKAROUND(m, x) m x
...


回答2:

You have to put ... after both Args and args here:

ObjectType* CreateObject(Args args)

and here:

object->Init(this, args);

then the code should be:

template< typename ObjectType, typename... Args >
ObjectType* CreateObject(Args... args)
{
    ObjectType* object = new ObjectType();
    [...]
    object->Init(this, args...);
    [...]
    return object;
}

Another problem is that Visual Studio 2012 doesn't support variadic templates, but Nov '12 release does, check if you have the most recent release of the compiler.

Also you don't need variadic macros to recreate new functions, you can specify ObjectType like this:

ObjectType* obj = CreateObject<ObjectType>(foo, 1, "hi");