Генерация объявления функции с использованием итерации макроса

Я пытаюсь сгенерировать объявление функции с помощью макроса

/* goal: generate int f(int a, float b) */
template<typename P>
struct ptype;

template<typename P>
struct ptype<void(P)> { typedef P type; };

#define NAMEe
#define COMMAe
#define COMMA ,

#define NAME(N) N PARAMS
#define PARAMS(P, ...) COMMA ## __VA_ARGS__ P NAME ## __VA_ARGS__
#define PARAM_ITER(P) P NAME

#define PROTO(R, N, P)  \
  ptype<void R>::type N (PARAM_ITER P (,e))

PROTO((int), f, (int)(a)(float)(b));

Он будет итеративно обрабатывать следующие (name) или (type) с помощью NAME или PARAMS соответственно, при этом ... имеет пустой аргумент макроса. Но GCC жалуется на

prototype.hpp:20:35: warning: ISO C99 requires rest arguments to be used

И clang жалуется на

ptype<void (int)>::type f (int aprototype.hpp:20:1: warning: varargs argument missing, but tolerated as an extension [-pedantic]

Я думаю, что это происходит из-за следующего

#define FOO(X, ...)
FOO(A);

Потому что я не передаю аргумент для ... или каждого из этих (name) или (type). Есть ли какая-нибудь простая работа, которую я могу применить?


Теперь я использовал метод, похожий на метод, используемый @James, чтобы найти длину списка параметров. Если вторым аргументом вместо O будет передано ONT, я напечатаю запятую и NAME. Ниже приводится окончательное решение:

/* goal: generate void f(int a, float b) */
template<typename P>
struct ptype;

template<typename P>
struct ptype<void(P)> { typedef P type; };

#define TYPE_DO(X) X
#define TYPE_DONT(X)
#define TYPE_MAYBE(X, A, ...) TYPE_D ## A (X)

#define COMMA_DO ,
#define COMMA_DONT
#define COMMA_MAYBE(A, B, ...) COMMA_D ## B

#define NAME_DO NAME
#define NAME_DONT
#define NAME_MAYBE(A, B, ...) NAME_D ## B

#define NAME(N) N PARAMS
#define PARAMS(...) COMMA_MAYBE(__VA_ARGS__,O,O) TYPE_MAYBE(__VA_ARGS__,O,O) \
                    NAME_MAYBE(__VA_ARGS__,O,O)
#define PARAM_ITER(P) P NAME

#define PROTO(R, N, P)  \
  ptype<void R>::type N (PARAM_ITER P (D,ONT))

Контрольная работа:

#define STR1(X) #X
#define STR(X) STR1(X)

int main() {
  // prints correctly
  std::cout << STR(PROTO((int), f, (int)(a)(float)(b)));
}

person Johannes Schaub - litb    schedule 18.03.2011    source источник


Ответы (2)


В P99 есть макрос, который, я думаю, делает именно то, что вам нужно, а именно P99_PROTOTYPE. Он имеет «подпись»

P99_PROTOTYPE(RT, NAME [, AT]*)

где RT — тип возвращаемого значения (может быть void), а AT — типы аргументов. Список типов аргументов может быть пустым, в этом случае он заменяется на void.

Помните, что P99 создан для C99, а не для C++. Вы столкнетесь с особыми трудностями, если ваши аргументы содержат запятые. Злоупотребление синтаксисом C++ токенов < и > в качестве заключенных в скобки выражений для шаблонов особенно вредно для препроцессора. C-препроцессор и C++ — принципиально несовместимые языки на уровне синтаксиса.

P99 избавляется от трудностей, с которыми вы сталкиваетесь, определяя количество аргументов, которые макрос получает при вызове, и по-разному реагирует на пограничные случаи.

person Jens Gustedt    schedule 19.03.2011
comment
Да, именно из-за запятой я заключаю возвращаемый тип в круглые скобки. Так что я мог бы использовать трюк void P, чтобы получить тип. Я изучу код P99_PROTOTYPE и посмотрю, как он работает. Спасибо! - person Johannes Schaub - litb; 20.03.2011
comment
@Johannes, в тех редких случаях, когда запятые создают трудности в P99, я использую P00_ROBUST для заключения выражения в скобки. Этот макрос просто возвращает свой список аргументов, но в контексте расширения макроса это имеет то преимущество, что запятые, разделяющие аргументы P00_ROBUST, не учитываются для аргументов другого макроса. Я не уверен, что C++ имеет тот же порядок оценки, что и C99, так что это может работать, а может и не работать. - person Jens Gustedt; 20.03.2011

Для решения проблемы "FOO" можно выбрать разные макросы в зависимости от арности пакета переменных аргументов. Вот первая попытка:

// These need to be updated to handle more than three arguments:
#define PP_HAS_ARGS_IMPL2(_1, _2, _3, N, ...) N
#define PP_HAS_ARGS_SOURCE() MULTI, MULTI, ONE, ERROR

#define PP_HAS_ARGS_IMPL(...) PP_HAS_ARGS_IMPL2(__VA_ARGS__)
#define PP_HAS_ARGS(...)      PP_HAS_ARGS_IMPL(__VA_ARGS__, PP_HAS_ARGS_SOURCE())

#define FOO_ONE(x)     ONE_ARG:    x
#define FOO_MULTI(...) MULTI_ARG:  __VA_ARGS__

#define FOO_DISAMBIGUATE2(has_args, ...) FOO_ ## has_args (__VA_ARGS__)
#define FOO_DISAMBIGUATE(has_args, ...) FOO_DISAMBIGUATE2(has_args, __VA_ARGS__)
#define FOO(...) FOO_DISAMBIGUATE(PP_HAS_ARGS(__VA_ARGS__), __VA_ARGS__)

Пример использования:

FOO(1)     // replaced by ONE_ARG:   1
FOO(1, 2)  // replaced by MULTI_ARG: 1, 2

(Я попытаюсь вернуться к этому, чтобы очистить его; я думаю, что там определенно есть ненужные макросы. У меня не было возможности изучить более широкую проблему, которую вы описываете, поэтому я не уверен, что это решит это тоже. Может быть и более простой способ решить эту проблему... Я не особо знаком с вариативными макросами. Это чисто препроцессирует на mcpp.)

person James McNellis    schedule 18.03.2011