7

我想强制预处理器为我做一些自动代码生成。我不需要太多:只是一个包含另一个 for 循环的简单 for 循环。[1]

我已经阅读了有关宏扩展的所有内容,并且当蓝色油漆出现时不再咯咯笑。在美好的一天,我什至可以解释为什么需要多层宏来生成带有标记粘贴的函数名称。我实际上已经让 for 循环工作了。但是当涉及到在循环中放置循环时,我只能随机使用 DEFER、EVAL 和 OBSTRUCT 并希望获得最好的结果。

我不会被理性的呼唤吓倒。我真的很想用标准的 C 预处理器来做这件事。我保证无论结果如何,我、我的雇主和我的继承人都不会以技术渎职为由起诉你。我保证我不会允许其他人在没有适当的安全眼镜的情况下维护代码,甚至查看它。假装你是否愿意,我只是出于理论上的兴趣而问。或者我唯一的其他选择是使用 M4:因为如果 CPP 中的递归宏很古怪,那么 M4 肯定是整只鸡。

我找到的最好的参考资料是一个 9 年前的 Usenet 线程: http ://comp.std.c.narkive.com/5WbJfCof/double-cpp-expansion

它开始偏离主题,语气有些琐碎和好斗,而且超出了我的想象。但我认为我寻求的答案就在某个地方。

下一个最好的是名为 Cloak 的 CPP 滥用标头的文档: https ://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms

它需要一种不同的迭代方法,也许可以满足我的需求。但这也是一个很好的概述。

这是一些简化的代码,以显示我卡在哪里。

重复.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))

样本.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] 轻描淡写。

4

4 回答 4

4

借助此处的答案(并研究P99ChaosOrderCloak),我认为我有一个相当简单和紧凑的概念证明(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) 诚然,当应用于递归预处理器宏时,“相当简单”的标准相当宽松。不过,它非常紧凑。

于 2013-06-19T02:43:56.353 回答
4

Vesa Karvonen 的“Order”库/语言绝对可以为您做到这一点。它在 C 预处理器中实现了无限制的递归和循环,并且作为一个非常酷的奖励,用“正确”编程语言的简洁语法修饰它(澄清:这不是替代预处理器,它只是做了很多标记-粘贴以保持其关键字简短。它仍然是纯CPP)。

它使用一种完全不同的技术,将您的元程序转换为 CPS,然后将它们传递给一个可能有数万亿步的循环结构,并以严格的线性方式执行元程序。因此,循环和递归函数可以嵌套任意深度,因为它们没有单独的驱动程序需要交互并将彼此涂成蓝色。

是的,确实有人使用 CPP 宏实现了完整的虚拟机和解释器。这很吓人。

(编辑:如果 Rosetta Code 也停止为您工作,请尝试存档版本。)

于 2013-06-17T12:52:33.400 回答
2

P99可能会为您提供您正在寻找的东西。它有几种类型的宏迭代器,简单的像P99_UNROLL, P99_SERetc 和通用的P99_FOR

于 2013-06-16T10:25:52.940 回答
1

我不确定我在那里遵循你所有的宏。这里的答案(现在也在这里)解释了如何创建通用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可以直接递归地再次调用该宏。所以这是你的OUTERINNER重复的宏:

#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");
}

希望这是有道理的。

于 2013-06-17T03:51:13.177 回答