5

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?

Community
  • 1
  • 1
user2683028
  • 93
  • 1
  • 4
  • 1
    The signature of variadic template should be `template< typename ObjectType, typename... Args > ObjectType* CreateObject(Args... args);`. But you'll need some context to expand the argument pack to. – jrok Aug 14 '13 at 16:38
  • Dynamic allocation, dumb pointers, and two-stage initialisation? This is a recipe for nightmarish debugging sessions. You should run away right now. – Mike Seymour Aug 14 '13 at 16:44

2 Answers2

9

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
...
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • Thank you very much for your detailed answer. Sadly, I cannot use either variadic templates nor Boost in my context. Your first solution works fine under GCC, but apparently Visual Studio doesn't expand __VA_ARGS__ in the same way, resulting in a serie of syntax errors when I use that solution with more than 1 parameter (cf. [link](http://stackoverflow.com/questions/2575864/the-problem-about-different-treatment-to-va-args-when-using-vs-2008-and-gcc) ). I am currently trying to adapt your solution with the workaround mentionned in that post, but have been unsuccessful so far. – user2683028 Aug 20 '13 at 14:39
  • @user2683028 I updated the answer to show the workarounds needed for visual studio. – Paul Fultz II Aug 20 '13 at 16:02
  • Thanks, I also found a workaround using the other post's solution : `#define EXPLICIT(...) __VA_ARGS__` and using it around NARGS and CAT. – user2683028 Aug 21 '13 at 07:53
0

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");
xorguy
  • 2,594
  • 1
  • 16
  • 14
  • Well, turns out I can't use variadic templates since we don't support that feature yet. Thank you for your answer and clarifications on the syntax! – user2683028 Aug 19 '13 at 13:00