Создание связанного x-макроса из существующего

Рассмотрим следующий пользовательский стиль x-macro:

#define PRIMES_X(func) \
  func(2) \
  func(3) \
  func(5) \
  func(7) 

Мы можем использовать это, чтобы неоднократно расширять переданный макрос func с первыми четырьмя простыми числами. Например:

#define MAKE_FUNC(num) void foo ## num();
PRIMES_X(MAKE_FUNC)

Объявляет функции, возвращающие void, foo2(), foo3(), foo5() и foo7().

Все идет нормально.

Допустим, я знаю, что хочу создать связанный x-макрос, который вызывает свой аргумент не с голыми простыми числами 2, 3, ..., а с некоторым производным от него токеном, таким как имена функций выше. То есть я хочу это:

#define PRIMES_FOO_X(func) \
  func(foo2) \
  func(foo3) \
  func(foo5) \
  func(foo7) 

но на самом деле не записывая все это (действительно, это выйдет из синхронизации в момент изменения PRIMES_X.

Я хочу определить PRIMES_FOO_X через PRIMES_X. Я почти могу добраться туда, например:

#define FOO_ADAPT(num) func(foo ## num)
#define PRIMES_FOO_X(f) PRIMES_X(FOO_ADAPT)

В этом случае PRIMES_FOO_X расширяется до:

  func(foo2) \
  func(foo3) \
  func(foo5) \
  func(foo7)

... что выглядит правильно, но func здесь не переданный аргумент, а просто токен func, поскольку FOO_ADAPT не имеет аргумента с именем func, только PRIMES_FOO_X(func) имеет (и он его не использует).

Я не могу понять, как это сделать.


person BeeOnRope    schedule 20.07.2018    source источник
comment
Было бы полезно, если бы вы могли привести один или два варианта использования, показывающих желаемый результат макроса PRIMES_FOO_X.   -  person Joseph Quinsey    schedule 21.07.2018
comment
@JosephQuinsey желаемое PRIMES_FOO_X расширение для PRIMES_FOO_X показано в конце моего вопроса: оно действительно должно вести себя точно так же, как если бы это был макрос #define PRIMES_FOO_X(func) ` followed by the text shown in my last code block. Of course, I don't want to write it out by hand but for it to have this effect but be based on the existing PRIMES_X` (и поэтому, в частности, изменения в PRIMES_X будут отражены в PRIMES_FOO_X ).   -  person BeeOnRope    schedule 22.07.2018


Ответы (4)


Ключевое наблюдение... учитывая это:

#define PRIMES_X(func) \
  func(2) \
  func(3) \
  func(5) \
  func(7)

PRIMES_X() расширяется до (2) (3) (5) (7), что с точки зрения метапрограммирования CPP представляет собой структуру данных последовательность. Имея это в виду, давайте начнем двигаться назад. Вы хотите что-то вроде этого:

#define PRIMES_FOO_X(func) \
  /* something that expands to: func(foo2) func(foo3) func(foo5) func(foo7) */

...и вы хотите, чтобы foo2, foo3, foo5, foo7 пришли из расширения PRIMES_X. Очевидно, тогда 2 становится foo2, 3 становится foo3 и т. д.; поэтому давайте предположим, что такие становления происходят в соответствии с макросом под названием FOOIDENT_OF. Затем в PRIMES_FOO_X нужно звонить func на (FOOIDENT_OF(2)) и т.д.; то есть вы хотите что-то более точное:

#define PRIMES_FOO_X(func) \
  /* something that expands to: \
   * func(FOOIDENT_OF(2)) func(FOOIDENT_OF(3)) \
   * func(FOOIDENT_OF(5)) func(FOOIDENT_OF(7)) */

Сочетая две идеи, мы имеем следующие элементы:

  • func, операция, применяемая в производном макросе X
  • FOOIDENT_OF, операция, которая преобразует каждый список аргументов X-макроса в новую форму
  • PRIMES_X(), последовательность всех списков аргументов

Это возможно и даже немного легко сделать, если мы используем последовательность препроцессора boost.

#include <boost/preprocessor/seq.hpp>

#define PAIR_ELEMENT_1(A,B) A
#define PAIR_ELEMENT_2(A,B) B

#define PAIR_XFORM_MACRO(r, data, elem) \
   PAIR_ELEMENT_1 data ( PAIR_ELEMENT_2 data (elem) )

#define PAIR_XFORM(PAIR_, SEQ_) \
   BOOST_PP_SEQ_FOR_EACH(PAIR_XFORM_MACRO, PAIR_, SEQ_)

Здесь у меня есть PAIR_XFORM, который принимает 2-кортеж ("пару") макросов и применяет их оба к каждому элементу последовательности. IOW, PAIR_XFORM((func, FOOIDENT_OF), PRIMES_X()) генерирует нашу цель. Теперь все, что нам нужно, это сгенерировать новый X-макрос и сделать макрос внутреннего преобразования:

#define FOOIDENT_OF(N) foo##N
#define PRIMES_FOO_X(func) PAIR_XFORM((func, FOOIDENT_OF), PRIMES_X())

Вот так это выглядит на stacked-crooked.

person H Walters    schedule 21.07.2018

Возможно, простого обходного пути будет достаточно.

Вместо того, чтобы передавать аргумент func в PRIMES_FOO_X, вы можете объявить его заранее. Например, в этом коде мы используем FOO_FUNC для хранения func:

#define PRIMES_FOO_X PRIMES_X(FOO_ADAPT)
#define FOO_ADAPT(num) FOO_FUNC(foo ## num)

#define FOO_FUNC bar
PRIMES_FOO_X

#undef  FOO_FUNC
#define FOO_FUNC(x) x();
PRIMES_FOO_X

Результат:

bar(foo2) bar(foo3) bar(foo5) bar(foo7)

foo2(); foo3(); foo5(); foo7();
person Joseph Quinsey    schedule 20.07.2018

(Примечание: это мой второй ответ на этот вопрос)

Вдохновленный ответом H Walters с использованием Boost, я хотел найти решение только для C. Отличный ответ Уильяма Суонсона на макрос Foreach для аргументов макросов, кажется, дает один.

Взяв код из его ответа, мы можем сгенерировать это решение:

// The first part here is taken from William Swanson's answer
// to https://stackoverflow.com/questions/6707148
#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL(...)  EVAL4(EVAL4(EVAL4(__VA_ARGS__)))

#define MAP_OUT
#define MAP_END(...)
#define MAP_GET_END() 0, MAP_END
#define MAP_NEXT0(item, next, ...) next MAP_OUT
#define MAP_NEXT1(item, next) MAP_NEXT0(item, next, 0)
#define MAP_NEXT(item, next)  MAP_NEXT1(MAP_GET_END item, next)

#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)
#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__ (), 0))

// This is the example given by the OP:
#define PRIMES_X(func) \
  func(2) \
  func(3) \
  func(5) \
  func(7)

#define FOO_LIST(num) foo ## num, // note comma
#define PRIMES_FOO_X(f) MAP(f, PRIMES_X(FOO_LIST))

#define XXX(x) bar(x)
#define YYY(x) x();
PRIMES_FOO_X(XXX)
PRIMES_FOO_X(YYY)

Результат с использованием gcc -E -P ...:

bar(foo2) bar(foo3) bar(foo5) bar(foo7)
foo2(); foo3(); foo5(); foo7();

Примечания:

  • В определении MAP мне пришлось удалить запятую после __VA_ARGS__, чтобы в конце не появилось лишнее мусорное значение. Но это нарушает макрос для других целей. Можно было бы подумать, что простое перемещение запятой в FOO_LIST исправит это, но это не так. (Необходимо: исправить)

  • Любое решение типа MAP или FOREACH, в котором используется оператор конкатенации ##, вряд ли будет здесь работать, потому что любой входной список, заданный макросом, не расширяется. (Это было ново для меня :()


Редактировать: второе альтернативное решение, использующее ту же идею, основано на коде из https://esolangs.org/wiki/ELIP. Вывод идентичен приведенному выше. (И это показывает, что мой комментарий о ## неверен.)

// The first part here is based on esolangs.org/wiki/ELIP (CC0 public domain)
// (Note MAP here is their FOREACH)
#define XCAT(x,y) x ## y
#define CAT(x,y) XCAT(x,y)
#define EMPTY()
#define LPAREN (
#define RPAREN )
#define DEFER(x) x EMPTY()
#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define TRUE(x,...) x
#define FALSE(x,...) __VA_ARGS__
#define TRANSFORM(seq, ...) CAT(TRANSFORM1_A seq,0END)(EAT, __VA_ARGS__)
#define TRANSFORM1_A(...) (EXPAND, __VA_ARGS__)() TRANSFORM1_B
#define TRANSFORM1_B(...) (EXPAND, __VA_ARGS__)() TRANSFORM1_A
#define TRANSFORM1_A0END
#define TRANSFORM1_B0END
#define RPXFRM(m, ...) m(RPAREN RPXFRM_ID)
#define RPXFRM_ID() RPXFRM
#define INFUSE(seq, ...) INFUSE5(INFUSE1(TRANSFORM(seq), __VA_ARGS__))
#define INFUSE1(xfrm, ...) INFUSE2 xfrm, __VA_ARGS__ RPXFRM xfrm
#define INFUSE2(m, ...) m(INFUSE3 DEFER(XCAT)(LPA,REN)(__VA_ARGS__), INFUSE2_ID)
#define INFUSE2_ID() INFUSE2
#define INFUSE3(...) INFUSE4(__VA_ARGS__)
#define INFUSE4(x, rest, ...) (__VA_ARGS__, EXPAND x)() rest, __VA_ARGS__
#define INFUSE5(...) INFUSE6(__VA_ARGS__)
#define INFUSE6(...) INFUSE7(__VA_ARGS__)
#define INFUSE7(seq, ...) seq
#define MAP(macro, seq) EXPAND(MAP1 INFUSE(seq, TRUE, macro)(FALSE, EAT,))
#define MAP1(p, m, ...) m(__VA_ARGS__) p(MAP1_ID)
#define MAP1_ID() MAP1

// This is the example given by the OP:
#define PRIMES_X(func) \
  func(2) \
  func(3) \
  func(5) \
  func(7)

#define FOO_LIST(num) (foo ## num) // parentheses, no commas
#define PRIMES_FOO_X(f) MAP(f, PRIMES_X(FOO_LIST))

#define XXX(x) bar(x)
#define YYY(x) x();
PRIMES_FOO_X(XXX)
PRIMES_FOO_X(YYY)
person Joseph Quinsey    schedule 21.07.2018

Попробуйте следующее решение:

#define PRIMES_X(func)                          \
  func(2) \
  func(3) \
  func(5) \
  func(7)

#define DERIVE_TOKEN(num) (foo##num);
#define FOO_ADAPT(f) f DERIVE_TOKEN
#define PRIMES_FOO_X(f) PRIMES_X(FOO_ADAPT(f))

Он расширяется

PRIMES_FOO_X(funct)

to

funct (foo2); funct (foo3); funct (foo5); funct (foo7);
person Marian    schedule 21.07.2018