Как написать рекурсивный макрос "повторить" для цикла, чтобы сгенерировать код C с препроцессором CPP?

Я хочу заставить препроцессор сделать для меня автоматическое создание кода. Мне не нужно много: простой цикл for, который содержит другой цикл for. [1]

Я читал все, что могу о расширении макросов, и больше не хихикаю, когда появляется синяя краска. В хороший день я могу даже объяснить, почему нужно многослойные макросы для генерации имени функции с помощью вставки меток. Я действительно получил работу for-loop. Но когда дело доходит до ввода цикла в цикл, я сводится к случайному разбрызгиванию с помощью DEFER, EVAL и OBSTRUCT и надеясь на лучшее.

Я не буду сдерживаться призывами к разуму. Я действительно хочу сделать это со стандартным препроцессором C. Я обещаю, что независимо от результата ни я, ни мой работодатель, ни мои наследники не будут судиться с вами за технологическую халатность. Я обещаю, что я не позволю кому-либо еще поддерживать код или даже просматривать его без соответствующих защитных очков. Притворяйся, если хочешь, я просто спрашиваю теоретический интерес. Или что мой единственный другой вариант использует M4: для, если рекурсивные макросы в CPP кажутся странными, конечно, M4 - это весь цыпленок.

Лучший справочный материал, который я нашел, - это 9-летняя тема Usenet: http://comp.std.c.narkive.com/5WbJfCof/double-cpp-expansion

Он начинается не по теме, немного мелочный и боевой по тону, и это путь над моей головой. Но я думаю, что ответ, который я ищу, где-то там.

Следующим лучшим является документация для заголовка злоупотребления CPP под названием Cloak: https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms

Это займет несколько иной подход к итерации и, возможно, будет служить моим потребностям. Но это также хороший обзор.

Вот несколько вырезанных кодов, чтобы показать, где я застрял.

repeat.h:

#define REPEAT(macro, times, start_n, next_func, next_arg, macro_args...) \
    _REPEAT_ ## times(macro, start_n, next_func, next_arg, ## macro_args)

#define REPEAT_ADD_ONE(macro, times, start_n, macro_args... )                    \
    REPEAT(macro, times, start_n, _REPEAT_ADD_ONE, 0, ## macro_args)

#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n

#define _REPEAT_0(args...)  /* empty */
#define _REPEAT_1(macro, n, func, i, args...) macro(n, ## args) 
#define _REPEAT_2(m, n, f, i, a...) m(n, ## a); _REPEAT_1(m, f(n, i), f, i, ## a)
#define _REPEAT_3(m, n, f, i, a...) m(n, ## a); _REPEAT_2(m, f(n, i), f, i, ## a)
#define _REPEAT_4(m, n, f, i, a...) m(n, ## a); _REPEAT_3(m, f(n, i), f, i, ## a)
#define _REPEAT_5(m, n, f, i, a...) m(n, ## a); _REPEAT_4(m, f(n, i), f, i, ## a)
#define _REPEAT_6(m, n, f, i, a...) m(n, ## a); _REPEAT_5(m, f(n, i), f, i, ## a)
#define _REPEAT_7(m, n, f, i, a...) m(n, ## a); _REPEAT_6(m, f(n, i), f, i, ## a)
#define _REPEAT_8(m, n, f, i, a...) m(n, ## a); _REPEAT_7(m, f(n, i), f, i, ## a)
#define _REPEAT_9(m, n, f, i, a...) m(n, ## a); _REPEAT_8(m, f(n, i), f, i, ## a)
#define _REPEAT_10(m, n, f, i, a...) m(n, ## a); _REPEAT_9(m, f(n, i), f, i, ## a)

#define _REPEAT_ADD_ONE_0 1
#define _REPEAT_ADD_ONE_1 2
#define _REPEAT_ADD_ONE_2 3
#define _REPEAT_ADD_ONE_3 4
#define _REPEAT_ADD_ONE_4 5
#define _REPEAT_ADD_ONE_5 6
#define _REPEAT_ADD_ONE_6 7
#define _REPEAT_ADD_ONE_7 8
#define _REPEAT_ADD_ONE_8 9
#define _REPEAT_ADD_ONE_9 10
#define _REPEAT_ADD_ONE_10 11

#define _REPEAT_ADD_0(x) x
#define _REPEAT_ADD_1(x) _REPEAT_ADD_ONE(x)
#define _REPEAT_ADD_2(x) _REPEAT_ADD_1(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_3(x) _REPEAT_ADD_2(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_4(x) _REPEAT_ADD_3(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_5(x) _REPEAT_ADD_4(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_6(x) _REPEAT_ADD_5(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_7(x) _REPEAT_ADD_6(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_8(x) _REPEAT_ADD_7(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_9(x) _REPEAT_ADD_8(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_10(x) _REPEAT_ADD_9(_REPEAT_ADD_ONE(x))

sample.c:

#include "repeat.h"

#define INNER_MACRO(inner, outer) if (inner == outer) printf("Match\n")
#define INNER_BLOCK  { if (inner == outer) printf("Match\n"); }

#define OUTER_MACRO_INNER_MACRO(outer) REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer)
#define OUTER_BLOCK_INNER_MACRO { REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer); }
#define OUTER_MACRO_INNER_BLOCK(outer) REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer)
#define OUTER_BLOCK_INNER_BLOCK { REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer); }

void outer_macro_inner_macro() {
    REPEAT_ADD_ONE(OUTER_MACRO_INNER_MACRO, 2, 1);
}

void outer_macro_inner_block() {
    REPEAT_ADD_ONE(OUTER_MACRO_INNER_BLOCK, 2, 1);
}

void outer_block_inner_macro() {
    REPEAT_ADD_ONE(OUTER_BLOCK_INNER_MACRO, 2, 1);
}

void outer_block_inner_block() {
    REPEAT_ADD_ONE(OUTER_BLOCK_INNER_BLOCK, 2, 1);
}

В sample.c я показал четыре варианта, которые приближаются к тому, что я хочу. Но их нет. Вот что я получаю как вывод с помощью "cpp sample.c > out.c; astyle out.c;"

void outer_macro_inner_macro() {
    REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 1);
    REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 2);
}

void outer_macro_inner_block() {
    REPEAT_ADD_ONE({ if (inner == outer) printf("Match\n"); }, 3, 0, 1);
    REPEAT_ADD_ONE({ if (inner == outer) printf("Match\n"); }, 3, 0, 2);
}

void outer_block_inner_macro() {
    {
        if (0 == outer) printf("Match\n");
        if (1 == outer) printf("Match\n");
        if (2 == outer) printf("Match\n");
    }(1);
    {
        if (0 == outer) printf("Match\n");
        if (1 == outer) printf("Match\n");
        if (2 == outer) printf("Match\n");
    }(2);
}

void outer_block_inner_block() {
    { {
            if (inner == outer) printf("Match\n");
        }(0, outer);
        {
            if (inner == outer) printf("Match\n");
        }(1, outer);
        {
            if (inner == outer) printf("Match\n");
        }(2, outer);
    }(1);
    { {
            if (inner == outer) printf("Match\n");
        }(0, outer);
        {
            if (inner == outer) printf("Match\n");
        }(1, outer);
        {
            if (inner == outer) printf("Match\n");
        }(2, outer);
    }(2);
}

И вот результат, который я хочу получить вместо:

void desired_results() {
   {
       if (0 == 1) printf("Match\n");
       if (1 == 1) printf("Match\n");
       if (2 == 1) printf("Match\n");
   };
   {
       if (0 == 2) printf("Match\n");
       if (1 == 2) printf("Match\n");
       if (2 == 2) printf("Match\n");
   };
}

По сути, я могу заставить работать, если я использую блок как тело внешнего цикла, но не если я использую макрос функции. Но мне нужно использовать макрос с аргументами, чтобы тела цикла могли использовать счетчик циклов как константу, а не как переменную.

Проблема с макросом - макросом заключается в том, что внутренний рекурсивный вызов REPEAT_ADD_ONE() не расширяется. Ответ, казалось бы, отложил бы расширение внутреннего цикла до тех пор, пока не будет создан внешний цикл, а затем вытеснит другой проход, который расширяет внутренний цикл. Но по какой-то причине мой подход "случайной обезьяны" к этому еще не создал решение...

[1] Предполагаемое занижение.

Ответ 1

Vesa Karvonen "Заказать" библиотека/язык может определенно сделать это за вас. Он реализует неограниченную рекурсию и цикл в препроцессоре C, и как действительно классный бонус наряжает его красивым кратким синтаксисом "правильного" языка программирования (чтобы уточнить: это не альтернативный препроцессор, он просто делает много токенов -pasting, чтобы сохранить ключевые слова короткими. Это все еще чистый CPP).

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

Да, действительно, кто-то реализовал полную виртуальную машину и интерпретатор с использованием макросов CPP. Это пугает.

(EDIT: попробуйте заархивированную версию, если Rosetta Code перестанет работать и для вас.)

Ответ 2

С помощью ответов здесь (и изучая P99, Chaos, Order и Cloak) Я думаю, что я имею достаточно простую и компактную концепцию (1). Поскольку я хотел только "повторить" функциональность, а не полный интерпретатор, я пошел несколько иначе, чем другие решения. Вместо того, чтобы создавать общие макросы "if", "while" или "when", я вместо этого непосредственно использовал серию макросов "декремента", которые расширяются до желаемого макроса плюс вызов макроса для n-1.

#ifndef _REPEAT_H
#define _REPEAT_H

// Usage: REPEAT_ADD_ONE(macro, times, start_n, macro_args... )
//        Recursion allowed if inner macros use REPEAT_ADD_ONE_INNER().
//        This demo header only allows 3 layers of recursion and max n=10.
//        Sample code at bottom.

#define REPEAT_ADD_ONE(macro, times, start_n, macro_args... )           \
    _REPEAT_EXPAND_3(REPEAT_ADD_ONE_INNER(macro, times, start_n, ## macro_args))

#define REPEAT_ADD_ONE_INNER(macro, times, start_n, macro_args... )     \
    _REPEAT_ ## times(macro, start_n, _REPEAT_ADD_ONE, ## macro_args)

#define _REPEAT_0(args...)  /* empty */
#define _REPEAT_1(macro, n, func, args...) _REPEAT_DEFER(macro)(n, ## args)
#define _REPEAT_2(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_1(m, f(n), f, ## a)
#define _REPEAT_3(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_2(m, f(n), f, ## a)
#define _REPEAT_4(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_3(m, f(n), f, ## a)
#define _REPEAT_5(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_4(m, f(n), f, ## a)
#define _REPEAT_6(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_5(m, f(n), f, ## a)
#define _REPEAT_7(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_6(m, f(n), f, ## a)
#define _REPEAT_8(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_7(m, f(n), f, ## a)
#define _REPEAT_9(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_8(m, f(n), f, ## a)
#define _REPEAT_10(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_9(m, f(n), f, ## a)
// ...

#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n
#define _REPEAT_ADD_ONE_0 1
#define _REPEAT_ADD_ONE_1 2
#define _REPEAT_ADD_ONE_2 3
#define _REPEAT_ADD_ONE_3 4
#define _REPEAT_ADD_ONE_4 5
#define _REPEAT_ADD_ONE_5 6
#define _REPEAT_ADD_ONE_6 7
#define _REPEAT_ADD_ONE_7 8
#define _REPEAT_ADD_ONE_8 9
#define _REPEAT_ADD_ONE_9 10
#define _REPEAT_ADD_ONE_10 11
// ...

#define _REPEAT_EMPTY()
#define _REPEAT_DEFER(token) token _REPEAT_EMPTY()

#define _REPEAT_EXPAND_3(args...) _REPEAT_EXPAND(_REPEAT_EXPAND(_REPEAT_EXPAND(args)))
#define _REPEAT_EXPAND(args...) args
// ...

#endif // _REPEAT_H

#ifdef SAMPLE_CODE
// to generate code:   cpp -DSAMPLE_CODE sample.c 
// or easier to read:  cpp -DSAMPLE_CODE sample.c > out.c; astyle out.c; less out.c
// to compile and run: gcc  -Wall -O3 -DSAMPLE_CODE sample.c -o sample

int printf(const char *format, ...);

#define BODY(i) printf("%d\n", i);
void simple(void) {
    REPEAT_ADD_ONE(BODY, 5, 1);
}

#define INNER(k, j, i) \
    printf("(%d, %d, %d)\n", i, j, k);          \
    if (i == j && j == k) printf("Match!\n")
#define MIDDLE(j, i) REPEAT_ADD_ONE_INNER(INNER, 2, 2, j, i)
#define OUTER(i) REPEAT_ADD_ONE_INNER(MIDDLE, 3, 0, i)
void recursive(void) {
    REPEAT_ADD_ONE(OUTER, 2, 1);
}

int main() {
    simple();
    recursive();
    return 0;
}

#endif // SAMPLE_CODE 

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

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

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

Спасибо за помощь!

(1) Правда, стандарт "достаточно простой" довольно свободен при применении к рекурсивным макросам препроцессора. Это довольно компактно.

Ответ 3

P99 может предоставить вам то, что вы ищете. Он имеет несколько типов итераторов макросов, простых, таких как P99_UNROLL, P99_SER и т.д. И общий P99_FOR.

Ответ 4

Не уверен, что я буду следить за всеми вашими макросами. Этот ответ здесь (также здесь теперь) объясняет, как создать общий макрос REPEAT, например:

#define REPEAT(count, macro, ...) \
    WHEN(count) \
    ( \
        OBSTRUCT(REPEAT_INDIRECT) () \
        ( \
            DEC(count), macro, __VA_ARGS__ \
        ) \
        OBSTRUCT(macro) \
        ( \
            DEC(count), __VA_ARGS__ \
        ) \
    )
#define REPEAT_INDIRECT() REPEAT

Это принимает счет, макрос и пользовательские данные. Поскольку макрос, переданный внутри, отложен, макрос REPEAT можно вызывать снова напрямую и рекурсивно. Итак, вот ваши макросы повторения OUTER и INNER:

#define OUTER(i, j) { REPEAT(j, INNER, i) }
#define INNER(j, i) if (j == INC(i)) printf("Match\n");

EVAL(REPEAT(2, OUTER, 3))

Это выведет это:

{ 
    if (0 == 1) printf("Match\n"); 
    if (1 == 1) printf("Match\n"); 
    if (2 == 1) printf("Match\n"); 
}
{
    if (0 == 2) printf("Match\n"); 
    if (1 == 2) printf("Match\n"); 
    if (2 == 2) printf("Match\n");
}

Надеюсь, это имеет смысл.