Использование вариативных макросов или шаблонов для реализации набора функций

У меня есть набор методов, используемых для создания и инициализации набора объектов. Все они выглядят почти одинаково, за исключением количества аргументов, которые передаются в функцию Init:

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

Обратите внимание, что аргументы не должны использоваться где-либо, кроме как для передачи в функцию Init.

Я хотел бы найти способ реализовать все это без дублирования кода для каждого типа объекта.


Я пробовал использовать макросы с переменным числом аргументов и получил следующий (неверный) результат:

#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)

Теперь в этой реализации я дважды использовал VA_ARGS, оба раза неправильно:

  • В первом случае я хочу иметь список аргументов с указанными мной типами (Arg1 a1, Arg2 a2 ...)

  • Во втором случае я хочу назвать эти аргументы их именами (Init (a1, a2 ...)).


Я пробовал использовать вариативные шаблоны:

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__);  \
}

... но это, похоже, тоже не работает, я получаю следующую ошибку в строке определения шаблона:

ошибка C2143: синтаксическая ошибка: отсутствует ',' перед '...'

ошибка C2065: 'Args': необъявленный идентификатор

Я использую VS2012.

Я все еще мог написать N похожих макросов для каждого количества аргументов, однако мне было интересно, есть ли способ получить тот же результат без дублирования кода?


person user2683028    schedule 14.08.2013    source источник
comment
Подпись вариативного шаблона должна быть template< typename ObjectType, typename... Args > ObjectType* CreateObject(Args... args);. Но вам понадобится некоторый контекст, чтобы расширить пакет аргументов.   -  person jrok    schedule 14.08.2013
comment
Динамическое размещение, немые указатели и двухэтапная инициализация? Это рецепт кошмарных сеансов отладки. Тебе следует бежать прямо сейчас.   -  person Mike Seymour    schedule 14.08.2013


Ответы (2)


Есть несколько способов решить эту проблему. Во-первых, вы можете использовать типизированное выражение в макросе, чтобы вы могли анализировать тип. Итак, CREATE_OBJECT_IMPL будет называться так:

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

Вот несколько макросов, которые извлекают тип и удаляют его:

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

Эти макросы работают следующим образом. Когда вы напишете STRIP((Arg1) arg1), он расширится до arg1. А когда вы напишете PAIR((Arg1) arg1), он расширится до Arg1 arg1. Теперь вам нужно применить эти макросы к каждому переданному аргументу, поэтому вот простой макрос APPLY, который позволит вам сделать это для 8 аргументов:

/* 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)

Затем вы можете определить CREATE_OBJECT_IMPL следующим образом:

#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; \
}

Конечно, вам могут понадобиться обходные пути для этих макросов, если вы используете их в Visual Studio. Конечно, лучшее решение - написать шаблонную функцию. Итак, вы бы назвали свой CreateObject так:

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

В C ++ 11 вы можете использовать такие шаблоны varidiac:

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

Но если ваш компилятор не поддерживает шаблоны varidiac, вы можете использовать Boost.PP для создания перегрузок до 10 аргументов (или более, если необходимо):

#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, ~)

Изменить: Вот обходные пути, которые вам понадобятся, чтобы заставить вышеуказанные макросы работать в 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
...
person Paul Fultz II    schedule 18.08.2013
comment
Большое спасибо за подробный ответ. К сожалению, я не могу использовать ни вариативные шаблоны, ни Boost в своем контексте. Ваше первое решение отлично работает в GCC, но, очевидно, Visual Studio не расширяет VA_ARGS таким же образом, что приводит к серии синтаксических ошибок, когда я использую это решение с более чем одним параметром (см. ссылка ). В настоящее время я пытаюсь адаптировать ваше решение с помощью обходного пути, упомянутого в этом сообщении, но пока безуспешно. - person user2683028; 20.08.2013
comment
@ user2683028 Я обновил ответ, чтобы показать обходные пути, необходимые для Visual Studio. - person Paul Fultz II; 20.08.2013
comment
Спасибо, я также нашел обходной путь, используя решение другого сообщения: #define EXPLICIT(...) __VA_ARGS__ и использую его для NARGS и CAT. - person user2683028; 21.08.2013

Вы должны поставить ... после Args и args здесь:

ObjectType* CreateObject(Args args)

и тут:

object->Init(this, args);

тогда код должен быть:

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

Другая проблема заключается в том, что Visual Studio 2012 не поддерживает вариативные шаблоны, но выпуск от ноября 2012 г. поддерживает, проверьте, установлена ​​ли у вас самая последняя версия компилятора.

Также вам не нужны вариативные макросы для воссоздания новых функций, вы можете указать ObjectType следующим образом:

ObjectType* obj = CreateObject<ObjectType>(foo, 1, "hi");
person xorguy    schedule 14.08.2013
comment
Что ж, оказывается, я не могу использовать вариативные шаблоны, поскольку мы еще не поддерживаем эту функцию. Спасибо за ответ и пояснения по синтаксису! - person user2683028; 19.08.2013