在C中扩展宏

时间:2015-10-03 14:57:15

标签: c macros overloading code-generation c11

所以我开始使用X-Macros在C中编写模板,并且希望使用_Generic()来重载它们。问题是需要扩展宏。我知道我不能拥有自我指涉的宏。而且我相信我无法像我希望的那样扩展宏的定义。我知道我可以使用其他技术并完成代码(比如从内到外翻转我的节点并使用container_of)但是在实现typeof()时我最终会遇到同样的问题所以我认为最好解决现在,如果可能的话。

一个玩具示例(我希望能够发挥作用)是:

#include <stdio.h>

void a(int x){printf("a\n");}
void b(char x){printf("b\n");}

#define mat(x) _Generic((x), int: a(x))
#define temp(x) mat(x)
#define mat(x) _Generic((x), char: b(x), default: temp(x))

int main(void)
    {
    int x;
    mat(x);
    }

有没有办法完成这种重新定义或类似的东西?如果没有办法以符合POSIX的方式(M4,sh,其他)做到这一点?

对于答案:此代码模式在C11中通常很有用,因此如果可能,仅使用C.我真的很讨厌大多数预处理器,所以如果它不是CPP那么它绝对是我的最后选择。

1 个答案:

答案 0 :(得分:1)

如果不从头开始重写,以任何方式更改宏都是不可能的。在标准6.10.3中,您有:

  

当前定义为类似对象的宏的标识符不应由另一个重新定义   #define预处理指令除非第二个定义是类似对象的宏   定义和两个替换列表是相同的。同样,当前是标识符   定义为类似函数的宏不应由另一个#define重新定义   预处理指令,除非第二个定义是类似函数的宏定义   具有相同数量和参数的拼写,并且两个替换列表是   相同。

所以,你要么根据它的定义精确地扩展宏,要么你需要#undef它。如果您#undef它,要重新定义它,您需要从头开始编写它。此时将无法使用旧定义,甚至有助于定义新版本。

您无法通过任何类型的中介“到达”旧定义,因为预处理器会对调用时活动的定义起作用,而不是在定义所涉及的宏的位置。所以,如果像你一样定义temp(x),它会在mat的新定义的上下文中扩展,而不是旧的定义(更不用说mat(x)的两个定义之间需要有一个undef)反递归规则将在mat(x)处停止宏扩展。

简而言之,根据标准,绝对没有办法以基于其原始定义的方式重新定义宏。从标准来看,这在数学上是可证明的。

您是否有任何理由不仅修改宏的原始定义来处理您感兴趣的每种类型?或者使用命名方案的一些修改来指示你自己的版本(比如追加_o来表示处理更多类型但最终依赖于相应宏的宏)?或者,更好的是,修改原始宏的名称,以便您可以重新定义它们但是可以使用原始文件吗?

修改

通过下面的评论,这里有一种方法可以实现OP的一些愿望。它并不完美,但C预处理器确实有局限性。这是一个将对象转换为可修改字符串的示例。

在首先定义宏的标题中写下这个:

#define get_length(x) get_length_help(x)
#define get_length_help(x) sizeof(#x)

#define to_string(x) _Generic( (x),                                        \
   int: sprintf( (char[get_length(INT_MAX)]){0}, "%d", x ),                \
   char: sprintf( (char[2]){0}, "%c", x ),                                 \
   to_string_1,
   to_string_2,
   // ...
   )

// make sure all extra spots expand to nothing until used
#define to_string_1
#define to_string_2
// ...

// macro to test if an extension slot is used
// requires that extensions do not begin with '('
#define used(...) used_help2( (used_help1 __VA_ARGS__ ()), 0, 1, )
#define used_help1() ),(
#define used_help2(_1,_2,_3,...) _3

在foo.c中写这个。

typedef struct {
   int x,
   int y
   } plot_point

// this part is important, all generic selections must be valid expressions
// for the other types as well
#define safe_plot_point(x) _Generic( x, plot_point: x, default: (plot_point){0,0} )

// this could define a comma delimited list of selection statements as well
#define to_string_foo                                                      \
   plot_point: sprintf(                                                    \
      (char[get_length((INT_MAX,INT_MAX))]){0},                            \
      "(%i,%i)",                                                           \
      safe_plot_point(x).x, safe_plot_point(x).y                           \
      )

// this is the only real complication for the user
#if used(to_string_1)
#undef to_string_1
#define to_string_1 to_string_foo
#elif used(to_string_2)
#undef to_string_2
#define to_string_2 to_string_foo
// ...
#else
_Static_assert( 0, "no to_string_#'s left!" );
#endif

所以,你可以获得一定程度的可修改性。请注意,没有宏可以只执行修改,因为宏是未定义的行为,以便宏扩展到另一个预处理指令,并且如上所述,预处理指令是更改宏行为的唯一方法。宏调用只是对其他调用视而不见,它们只看到其他定义。

另请注意,您可以定义任意数量的扩展槽。您可以定义比给定文件检查更多的插槽,并且只在需要时添加额外的行(#if)。