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

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

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

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

5.05.2009 12:26:20
+1 для тега «злоупотребление препроцессором» :)
Jason S 5.05.2009 12:29:52
stackoverflow.com/questions/6707148 демонстрирует взаимную рекурсию (наиболее одобренный, но не принятый ответ)
Slava 14.04.2017 16:19:31
6 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

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

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

12
5.05.2009 12:29:35
Я приму это как самый правильный ответ, невозможно (и не должно быть) создавать рекурсивные операторы препроцессора.
Christoffer 6.05.2009 20:27:05
Предполагается, что это невозможно, но не потому, что препроцессор является однопроходным. Процесс подстановки макросов является рекурсивным и многократным, благодаря тому, что стандарты называют «повторное сканирование и дальнейшая замена». Рекурсия и corecursion запрещены специально определенным механизмом. Но это все еще можно победить. Что касается рекурсии с использованием этого синтаксиса , то это невозможно, потому что отсутствие перегрузки не позволяет написать завершающий регистр.
Potatoswatter 26.03.2012 04:35:02
Вы можете заставить препроцессор выполнять несколько проходов над файлом #include __FILE__.
Paul Brannan 27.04.2017 16:12:16

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

5
5.05.2009 12:32:37
+1. Шаблоны Variadic входят в C ++ 1x, и g ++ уже реализует их, но пройдет некоторое время, прежде чем они будут широко приняты. К сожалению, решение для метапрограммирования шаблонов без них по-прежнему потребует уродливого вложения терминов (например, «max_of <3, max_of <4, max_of <5, 6>>>»), если вы не вручную «развернете» их (что вы могли бы сделать так же легко с макросами препроцессора). Шаблоны все еще лучше, потому что они могут повторяться и не оценивать аргументы более одного раза.
j_random_hacker 5.05.2009 12:44:41

Вы можете рассмотреть этот обман, поскольку он не является рекурсивным и не выполняет работу в препроцессоре. И он использует расширение 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. Но я не уверен, что макросы Variadic также есть в 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);
}
11
5.05.2009 13:18:42
На самом деле, макросы с переменным числом не являются частью C ++, поэтому практически любая комбинация становится здесь волосатой. :)
quinmars 5.05.2009 16:04:45
Ах, хорошая мысль, хотя они доступны по умолчанию в g ++. Я на самом деле думал, что это волосатая вещь, потому что первый элемент в списке параметров не обязательно тот, который имеет наиболее разумный общий тип. Очень плохие вещи могут произойти, если инициализация массива нарезает объекты или (в C) выполняет неправильные преобразования.
Steve Jessop 5.05.2009 16:43:45

Я думаю, что даже если бы вы могли рекурсивно расширять макросы, в вашем подходе с точки зрения эффективности возникла бы одна небольшая проблема ... когда макросы расширяются, если они 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))))

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

7
21.05.2009 16:00:23
Ха, мне это нравится Что не нравится в использовании Python в качестве препроцессора C? :)
Christoffer 22.05.2009 07:05:15
Вы можете использовать любой классный язык, который вам нравится, в качестве препроцессора ... и учитывая количество паренов, возможно, LISP может быть очень забавным! XD или, возможно, Perl one liner, так что вы можете передать макросы через флаг -D компилятору в командной строке ;-)
fortran 22.05.2009 08:40:44

Можно написать макрос, который оценивает количество аргументов, с которыми он вызывается. (Я не смог найти ссылку на место, где я впервые ее увидел.) Таким образом, вы можете написать 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.)

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

40
19.02.2011 03:31:18
Почему у вас _MAX_OF_N2, какова его функция?
ulidtko 21.11.2011 00:08:16
Это просто, чтобы заставить один уровень расширения. Если бы я поместил _MAX_OF_N3 (NUM_ARGS ( VA_ARGS ) и т. Д.) ", Пропустив промежуточный шаг, он был бы расширен до MAX_OF_NUMARGS ( VA_ARGS ) (и т.
DS. 21.11.2011 20:19:17
@ Ameen Макросы Variadic были введены в языке C в 1999 году. Visual Studio "2017" недостаточно современен, поскольку он еще не полностью обновлен до 1999 года. Вместо этого используйте современный компилятор C.
Lundin 16.03.2018 08:58:45

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

#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 ++, потому что препроцессор C не понимает C ++. Это просто замена текста. Но C ++ предоставляет функцию под названием «функция», которая будет работать с выражениями 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...));
}

Теперь приведенный выше код требует компилятора C ++ 11. Если вы используете C ++ 03, вы можете создать несколько перегрузок функции для имитации переменных параметров. Кроме того, мы можем использовать препроцессор для генерации этого повторяющегося кода для нас (вот для чего он предназначен). Итак, в C ++ 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, ~) 
4
27.03.2012 22:33:49