Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using variadic macros or templates to implement a set of functions

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?

like image 992
user2683028 Avatar asked Aug 14 '13 16:08

user2683028


People also ask

What is variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What are variadic functions in C?

Variadic functions are functions that can take a variable number of arguments. In C programming, a variadic function adds flexibility to the program. It takes one fixed argument and then any number of arguments can be passed.

What is a macro in C++?

A macro is defined as the piece of code that is replaced by the value of the macro in the program. We can define the macro by using the #define directive. Whenever a compiler encounters the macro name, it replaces it with the definition of the macro.

What are the macros that are designed to support variable length arguments?

To support variable length arguments in macro, we must include ellipses (…) in macro definition. There is also “__VA_ARGS__” preprocessing identifier which takes care of variable length argument substitutions which are provided to macro.


1 Answers

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
...
like image 106
Paul Fultz II Avatar answered Nov 14 '22 21:11

Paul Fultz II