Подтвердить что ты не робот

Вариадические рекурсивные макросы препроцессора - возможно ли это?

У меня возникла небольшая теоретическая проблема. В куске кода я поддерживаю там набор макросов вроде

#define MAX_OF_2(a, b)       (a) > (b) ? (a) : (b)
#define MAX_OF_3(a, b, c)    MAX_OF_2(MAX_OF_2(a, b), c)
#define MAX_OF_4(a, b, c, d) MAX_OF_2(MAX_OF_3(a, b, c), d)
...etc up to MAX_OF_8

Что бы я хотел сделать, это заменить их следующим:

/* Base case #1, single input */
#define MAX_OF_N(x)      (x)

/* Base case #2, two inputs */
#define MAX_OF_N(x, y)   (x) > (y) ? (x) : (y)

/* Recursive definition, arbitrary number of inputs */
#define MAX_OF_N(x, ...) MAX_OF_N(x, MAX_OF_N(__VA_ARGS__))

... который, конечно, не является допустимым кодом препроцессора.

Игнорируя, что этот конкретный случай, вероятно, должен быть решен с помощью функции, а не макроса препроцессора, можно ли определить переменный макрос MAX_OF_N()?

Просто для ясности конечный результат должен быть единственным макросом, который принимает произвольное количество параметров и оценивает их наибольшую из них. У меня странное чувство, что это должно быть возможно, но я не вижу, как это сделать.

4b9b3361

Ответ 1

Нет, поскольку препроцессор выполняет только один "промах" в файле. Там нет способа заставить его рекурсивно определять макросы.

Единственный код, который я видел, что-то вроде этого, не был вариационным, а использовал значения по умолчанию, которые должен был пройти пользователь:

x = MAX_OF_8 (a, b, -1, -1, -1, -1, -1, -1)

при условии, что все значения неотрицательны.

Функции Inline должны дать вам то же самое для С++. Как вы заявляете, вероятно, лучше оставить функцию с переменными аргументами, аналогичными printf().

Ответ 2

Можно написать макрос, который оценивает количество аргументов, с которыми он звонил. (Я не мог найти ссылку на место, где я впервые увидел это.) Таким образом, вы можете написать MAX_OF_N(), который будет работать так, как вам хотелось бы, но вам все равно понадобится все пронумерованные макросы до определенного предела:

#define MAX_OF_1(a)         (a)         
#define MAX_OF_2(a,b)       max(a, b)

#define MAX_OF_3(a,...)    MAX_OF_2(a,MAX_OF_2(__VA_ARGS__))
#define MAX_OF_4(a,...)    MAX_OF_2(a,MAX_OF_3(__VA_ARGS__))
#define MAX_OF_5(a,...)    MAX_OF_2(a,MAX_OF_4(__VA_ARGS__))
...
#define MAX_OF_64(a,...)   MAX_OF_2(a,MAX_OF_63(__VA_ARGS__))

// NUM_ARGS(...) evaluates to the literal number of the passed-in arguments.
#define _NUM_ARGS2(X,X64,X63,X62,X61,X60,X59,X58,X57,X56,X55,X54,X53,X52,X51,X50,X49,X48,X47,X46,X45,X44,X43,X42,X41,X40,X39,X38,X37,X36,X35,X34,X33,X32,X31,X30,X29,X28,X27,X26,X25,X24,X23,X22,X21,X20,X19,X18,X17,X16,X15,X14,X13,X12,X11,X10,X9,X8,X7,X6,X5,X4,X3,X2,X1,N,...) N
#define NUM_ARGS(...) _NUM_ARGS2(0, __VA_ARGS__ ,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)

#define _MAX_OF_N3(N, ...) MAX_OF_ ## N(__VA_ARGS__)
#define _MAX_OF_N2(N, ...) _MAX_OF_N3(N, __VA_ARGS__)
#define MAX_OF_N(...)      _MAX_OF_N2(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

Теперь MAX_OF_N(a,b,c,d,e) будет оцениваться до max(a, max(b, max(c, max(d, e)))). (Я тестировал gcc 4.2.1.)

Обратите внимание, что критический момент, когда базовый регистр (MAX_OF_2) не повторяет свои аргументы более одного раза в расширении (именно поэтому я положил max в этом примере). В противном случае вы удвоите длину расширения для каждого уровня, чтобы представить, что произойдет с 64 аргументами:)

Ответ 3

Вы можете рассмотреть этот обман, так как он не рекурсивный, и он не выполняет работу в препроцессоре. И он использует расширение GCC. И он работает только для одного типа. Это, однако, переменный макрос MAX_OF_N:

#include <iostream>
#include <algorithm>

#define MAX_OF_N(...) ({\
        int ra[] = { __VA_ARGS__ }; \
        *std::max_element(&ra[0], &ra[sizeof(ra)/sizeof(int)]); \
    })

int main() {
    int i = 12;
    std::cout << MAX_OF_N(1,3,i,6);
}

О да, и из-за потенциального выражения переменных в списке инициализаторов я не думаю, что эквивалент этого (используя его собственную функцию, чтобы избежать std:: max_element) будет работать на C89. Но я не уверен, что переменные макросы также находятся в C89.

Здесь что-то, что я думаю, обходит ограничение "только одного типа". Это становится немного волосатым, хотя:

#include <iostream>
#include <algorithm>

#define MAX_OF_N(x, ...) ({\
        typeof(x) ra[] = { (x), __VA_ARGS__ }; \
        *std::max_element(&ra[0], &ra[sizeof(ra)/sizeof(ra[0])]); \
    })

int main() {
    int i = 12;
    std::cout << MAX_OF_N(i+1,1,3,6,i);
}

Ответ 4

Я думаю, что даже если вы могли бы рекурсивно расширять макросы, была бы небольшая проблема с вашим подходом с точки зрения эффективности... когда макросы расширяются, если MAX_OF_[N-1] больше, то вам нужно оценить это снова с нуля.

Вот глупый и глупый ответ, который, вероятно, никому не понравится xD

файл "source.c"

#include "my_macros.h"
...

файл "Makefile"

myprogram: source.c my_macros.h
 gcc source.c -o myprogram

my_macros.h: make_macros.py
 python make_macros.py > my_macros.h

файл "make_macros.py"

def split(l):
    n = len(l)
    return l[:n/2], l[n/2:]

def gen_param_seq(n):
    return [chr(i + ord("A")) for i in range(n)]

def make_max(a, b):
    if len(a) == 1:
        parta = "("+a[0]+")"
    else:
        parta = make_max(*split(a))

    if len(b) == 1:
        partb = "("+b[0]+")"
    else:
        partb = make_max(*split(b))

    return "("+parta +">"+partb+"?"+parta+":"+partb+")"

for i in range(2, 9):
    p = gen_param_seq(i)
    print "#define MAX_"+str(i)+"("+", ".join(p)+") "+make_max(*split(p))

тогда у вас будут определены эти симпатичные макросы:

#define MAX_2(A, B) ((A)>(B)?(A):(B))
#define MAX_3(A, B, C) ((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))
#define MAX_4(A, B, C, D) (((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D)))
#define MAX_5(A, B, C, D, E) (((A)>(B)?(A):(B))>((C)>((D)>(E)?(D):(E))?(C):((D)>(E)?(D):(E)))?((A)>(B)?(A):(B)):((C)>((D)>(E)?(D):(E))?(C):((D)>(E)?(D):(E))))
#define MAX_6(A, B, C, D, E, F) (((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))>((D)>((E)>(F)?(E):(F))?(D):((E)>(F)?(E):(F)))?((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C))):((D)>((E)>(F)?(E):(F))?(D):((E)>(F)?(E):(F))))
#define MAX_7(A, B, C, D, E, F, G) (((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))>(((D)>(E)?(D):(E))>((F)>(G)?(F):(G))?((D)>(E)?(D):(E)):((F)>(G)?(F):(G)))?((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C))):(((D)>(E)?(D):(E))>((F)>(G)?(F):(G))?((D)>(E)?(D):(E)):((F)>(G)?(F):(G))))
#define MAX_8(A, B, C, D, E, F, G, H) ((((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D)))>(((E)>(F)?(E):(F))>((G)>(H)?(G):(H))?((E)>(F)?(E):(F)):((G)>(H)?(G):(H)))?(((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D))):(((E)>(F)?(E):(F))>((G)>(H)?(G):(H))?((E)>(F)?(E):(F)):((G)>(H)?(G):(H))))

и самое лучшее в этом состоит в том, что... он работает ^ _ ^

Ответ 5

Если вы идете по этой дороге на С++, посмотрите метапрограммирование шаблона. Это не очень, и это может не решить вашу точную проблему, но она будет обрабатывать рекурсию.

Ответ 6

Во-первых, макросы не расширяются повторно. Хотя макросы могут иметь возможность повторного входа, создав макрос для каждого уровня рекурсии, а затем выведя уровень рекурсии. Тем не менее, все это повторение и вывод рекурсии позаботится об библиотеке Boost.Preprocessor. Поэтому вы можете использовать макрос сложения более высокого порядка для вычисления max:

#define MAX_EACH(s, x, y) BOOST_PP_IF(BOOST_PP_GREATER_EQUAL(x, y), x, y)
#define MAX(...) BOOST_PP_SEQ_FOLD_LEFT(MAX_EACH, 0, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) 

MAX(3, 6, 8) //Outputs 8
MAX(4, 5, 9, 2) //Outputs 9

Теперь это будет понимать буквальные числа между 0-256. Он не будет работать с переменными С++ или выражением, потому что препроцессор C не понимает С++. Его просто чистая замена текста. Но С++ предоставляет функцию, называемую "функцией", которая будет работать с выражениями С++, и вы можете использовать ее для вычисления максимального значения.

template<class T>
T max(T x, T y)
{
    return x > y ? x : y;
}

template<class X, class... T>
auto max(X x, T ... args) -> decltype(max(x, max(args...)))
{
    return max(x, max(args...));
}

Теперь для кода выше требуется компилятор С++ 11. Если вы используете С++ 03, вы можете создать несколько перегрузок функции, чтобы имитировать вариационные параметры. Кроме того, мы можем использовать препроцессор для создания этого повторяющегося кода для нас (для чего он нужен). Поэтому в С++ 03 вы можете написать это:

template<class T>
T max(T x, T y)
{
    return x > y ? x : y;
}

#define MAX_FUNCTION(z, n, data) \
template<class T> \
T max(T x, BOOST_PP_ENUM_PARAMS(n, T x)) \
{ \
    return max(x, max(BOOST_PP_ENUM_PARAMS(n, x)));\
}

BOOST_PP_REPEAT_FROM_TO(2, 64, MAX_FUNCTION, ~)