C预处理器递归宏

10
#define PP_ARG0_(arg0, ...) arg0
#define PP_REST_(arg0, ...) __VA_ARGS__
#define PP_ARG0(args) PP_ARG0_ args
#define PP_REST(args) PP_REST_ args

#define FUNCTION(name) void name();
#define FUNCTION_TABLE(...)                   \
    FUNCTION(PP_ARG0((__VA_ARGS__)))          \
    FUNCTION_TABLE(PP_REST((__VA_ARGS__)))    \

测试代码:

FUNCTION_TABLE(f1, f2,f3,testA,testB,testC);

显然,由于递归扩展,它只会声明void f1();,其余部分不会被展开:

void f1(); FUNCTION_TABLE(f2,f3,testA,testB,testC);

我应该使用什么技巧来实现这种情况下的递归展开?问题是我需要支持许多参数(多达100个),而且绝对不能使用boost库。


你最终想要实现什么? - Greg A. Woods
这是使用c协处理器的自动代码生成器的过度简化版本。最终,我得出结论,没有办法在不为每个迭代编写单独宏的情况下完成它。也就是说,要支持具有100个参数的FUNCTION_TABLE,我必须编写100个定义,如FUNCTION_TABLE_1,FUNCTION_TABLE_2等。 - Pavel P
3个回答

10

最简单的解决方案是像这样使用序列迭代:

#define CAT(x, y) PRIMITIVE_CAT(x, y)
#define PRIMITIVE_CAT(x, y) x ## y

#define FUNCTION(name) void name();
#define FUNCTION_TABLE(seq) CAT(FUNCTION_TABLE_1 seq, _END)
#define FUNCTION_TABLE_1(x) FUNCTION(x) FUNCTION_TABLE_2
#define FUNCTION_TABLE_2(x) FUNCTION(x) FUNCTION_TABLE_1
#define FUNCTION_TABLE_1_END
#define FUNCTION_TABLE_2_END

然后您使用预处理器序列而不是可变参数调用 FUNCTION_TABLE

FUNCTION_TABLE((f1)(f2)(f3)(testA)(testB)(testC))

这不仅更简单,而且比使用递归解决方案(如您展示的方案或此处的方案)更快(即编译速度更快)。


我明白了。但在我的情况下,我需要传递函数返回类型、函数名称、函数参数和函数参数看起来像这样:(int, int, const char*),因此,如果我使用序列迭代,那么所有的括号会让人感到困惑:(int)(doSomething)(int, int, const char*),而不是 int, doSomething,(int, int, const char*) - Pavel P
实际上,我的迭代部分不处理函数声明;迭代部分处理可能有1个或256个函数的函数表。我可能会更新我的代码,看看它是否能更好地工作。 - Pavel P
1
@Pavel 如果你想避免太多嵌套的括号,你仍然可以像这样做:FUNCTION_TABLE((int, f1, int, int, const char*)(int, f2, int, int, const char*)),如果你将你的 FUNCTION 宏定义为 #define FUNCTION(ret, name, ...) ret name(__VA_ARGS__);。或者如果这不起作用,你可以更好地格式化以避免混淆,例如将每个函数放在单独的一行上。 - Paul Fultz II
1
这个解决方案很棒。但我不明白它是如何工作的。据我所知,一个标记在同一范围内不会扩展两次。因为它已经被“涂成蓝色”了。当FUNCTION_TABLE_2被扩展时,宏FUNCTION_TABLE_1应该已经被涂成蓝色了。为什么它还会被扩展呢? - Yyao
1
@Yyao FUNCTION_TABLE_2没有在FUNCTION_TABLE_1内展开,因此具有不同的禁用上下文,因此它永远不会被涂成蓝色。 - Paul Fultz II
我尝试过了,我相当确定如果不使用格式() () ()而是使用( , , ),这种方法根本行不通,因为函数式宏依赖于它才能与任何参数一起工作。 - Andrew

4

如果有人想要做同样的事情,这里是答案。

#define _PP_0(_1, ...) _1            // (a,b,c,d) => a
#define _PP_X(_1, ...) (__VA_ARGS__) // (a,b,c,d) => (b,c,d)

//for each a in __VA_ARGS__ do f(a,x) 
//where x is some parameter passed to PP_TRANSFORM
#define PP_TRANSFORM(f,x,...) \
    PP_JOIN(PP_TRANSFORM_,PP_NARG(__VA_ARGS__))(f,x,(__VA_ARGS__))

#define PP_TRANSFORM_0(...)
#define PP_TRANSFORM_1( f,x,a) f(_PP_0 a,x) PP_TRANSFORM_0( f,x,_PP_X a)
#define PP_TRANSFORM_2( f,x,a) f(_PP_0 a,x) PP_TRANSFORM_1( f,x,_PP_X a)
...
#define PP_TRANSFORM_51(f,x,a) f(_PP_0 a,x) PP_TRANSFORM_50( f,x,_PP_X a)
...
#define PP_TRANSFORM_99(f,x,a) f(_PP_0 a,x) PP_TRANSFORM_98(f,x,_PP_X a)
#define PP_TRANSFORM_100(f,x,a)f(_PP_0 a,x) PP_TRANSFORM_99(f,x,_PP_X a)

这里,PP_NARG 是用于计算参数数量的宏,PP_JOIN 是用于连接标记的宏(即 PP_JOIN(a,b) => ab)。如果您希望能够处理超过 64 个参数,则还需要修补 PP_NARG

现在,回到原始问题。使用 PP_TRANSFORM 的解决方案如下:

#define FUNCTION(name, dummy) void name();
#define FUNCTION_TABLE(...) PP_TRANSFORM(FUNCTION,dummy,__VA_ARGS__)

如果您想生成C ++实现函数,那么可以使用PP_TRANSFORM的不透明X参数:

#define FUNCTION_CPP(name, class) void class::name(){}
#define FUNCTION_TABLE_CPP(...) PP_TRANSFORM(FUNCTION_CPP,MyClass,__VA_ARGS__)

这一切同样适用于GCC和MSVC预处理器;PP_TRANSFORM_NN不使用__VA_ARGS__,以避免为GCC和MSVC分别实现100个定义。


1

我曾经很费劲地理解这个序列迭代的概念,但是通过拼凑上面的答案这里的答案,并且一点一点地逐步实践... - 我相信我已经弄明白了,并且知道如何解释和理解它。


这里有一些友好的代码,我进行了一些调整:

  • 重命名宏(我发现给宏命名更合理可以帮助我更好地理解过程)
  • 通过__VA_OPT__提供可选的空参数选项(如果不需要可以删除该部分)
  • 重新排列宏的顺序,使其与我的当前编译器g++兼容 (严格来说,这段代码是c++而不是c,但在这里基本上是相同的。)
  • 提供交换用于此递归过程的可变参数宏的能力

请注意,参数必须(() () ())的格式指定,而不是( , , ),因为函数样式的宏依赖于它才能与任何参数一起使用。 请参阅下面的代码,以获得深入的解释性演练。

#include <iostream>

#define CONCAT(a, ...) a ## __VA_ARGS__
#define CONCAT_FUNC(a, ...) CONCAT(a, __VA_ARGS__)

//Whatever is inside each () will be prepended and appended with what's here.
#define MyVariadicMacro(...) << __VA_ARGS__ + 7 << " "
#define MyVariadicMacro2(...) << __VA_ARGS__ << " "

#define RESOLVE_A(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__)) RESOLVE_B
#define RESOLVE_B(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__)) RESOLVE_A
#define RESOLVE_A_END
#define RESOLVE_B_END
#define RECURSE(...) CONCAT_FUNC(RESOLVE_A __VA_ARGS__, _END)

int main()
{
  //Choose your own variadic macro, to provide what to prepend and append to each variadic argument!
  #define VARIADIC_FUNC MyVariadicMacro

  //Note: Empty ()'s are in here just to provide an example that they can be ignored via. __VA_OPT__().
  std::cout RECURSE(() (0) () (1) (2) (3) ());

  //Swapping out with variadic macro is being utilized for RECURSE.
  #undef VARIADIC_FUNC
  #define VARIADIC_FUNC MyVariadicMacro2

  std::cout RECURSE(() (0) () (1) (2) (3) ());

  #undef VARIADIC_FUNC

  return 0;
}

输出:7 8 9 10 0 1 2 3

//Starting with:
std::cout RECURSE(() (0) () (1) (2) (3) ());

//Apply: #define RECURSE(...) CONCAT_FUNC(RESOLVE_A __VA_ARGS__, _END)
std::cout CONCAT_FUNC(RESOLVE_A() (0) () (1) (2) (3) (), _END);

//Apply: #define CONCAT_FUNC(a, ...) CONCAT(a, __VA_ARGS__)
std::cout CONCAT(RESOLVE_A() (0) () (1) (2) (3) (), _END);

//Apply: #define CONCAT(a, ...) a ## __VA_ARGS__
std::cout RESOLVE_A() (0) () (1) (2) (3) () ## _END;

//Apply: #define RESOLVE_A(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__)) RESOLVE_B
//Note: Since the () is empty, the __VA_OPT__() part is simply skipped.
std::cout RESOLVE_B(0) () (1) (2) (3) () ## _END;

//Apply: #define RESOLVE_B(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__)) RESOLVE_A
std::cout VARIADIC_FUNC(0) RESOLVE_A() (1) (2) (3) () ## _END;

//Apply: #define MyVariadicMacro(...) << __VA_ARGS__ + 7 << " "
//Apply: #define VARIADIC_FUNC MyVariadicMacro
std::cout << 0 + 7 << " " RESOLVE_A() (1) (2) (3) () ## _END;

//Apply: #define RESOLVE_A(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__)) RESOLVE_B
//Note: Since the () is empty, the __VA_OPT__() part is simply skipped.
std::cout << 0 + 7 << " " RESOLVE_B(1) (2) (3) () ## _END;

//And so on... ending up with:
//Note: Ending with empty () or non-empty() doesn't matter; either way, we will end up with a RESOLVE_?_END.
std::cout << 0 + 7 << " " << 1 + 7 << " " << 2 + 7 << " " << 3 + 7 << " " RESOLVE_A() ## _END;

//Apply: #define RESOLVE_A(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__)) RESOLVE_B
//Note: Since the () is empty, the __VA_OPT__() part is simply skipped.
std::cout << 0 + 7 << " " << 1 + 7 << " " << 2 + 7 << " " << 3 + 7 << " " RESOLVE_B ## _END;

//Apply: ## simply concatenates.
std::cout << 0 + 7 << " " << 1 + 7 << " " << 2 + 7 << " " << 3 + 7 << " " RESOLVE_B_END;

//Apply: #define RESOLVE_B_END
//Note: In this particular case, we happened to end up with RESOLVE_B_END; in other cases, we will end with
//RESOLVE_A_END.
std::cout << 0 + 7 << " " << 1 + 7 << " " << 2 + 7 << " " << 3 + 7 << " ";

//Etc.
std::cout << 7 << " " << 8 << " " << 9 << " " << 10 << " ";

注意:如果您想在X宏概念中使用RECURSE,则需要额外做一些事情。 X宏的问题在于,您必须像这样定义它以在递归宏中使用:
#define MyThing (() (0) () (1) (2) (3) ())

当你像这样使用它时,无论是通过RECURSE()还是其他任何宏,它都会被额外的括号包裹起来。
//It interprets this as RECURSE((() (0) () (1) (2) (3) ())), which is bad.
std::cout RECURSE(MyThing);

解决方案是使用像这样的宏,让它自然地解析以删除括号。以下是修改RECURSE()的示例:
#define ESCAPE_PAREN(...) __VA_ARGS__

//Old RECURSE():
#define RECURSE(...) CONCAT_FUNC(RESOLVE_A __VA_ARGS__, _END)

//New RECURSE():
#define RECURSE(...) CONCAT_FUNC(RESOLVE_A ESCAPE_PAREN __VA_ARGS__, _END)

//Alternatively, just use this instead of RECURSE() (seems to work better):
#define RECURSE_ESCAPE(...) ESCAPE_PAREN __VA_ARGS__

需要注意的是,当使用ESCAPE_PAREN时,不会将__VA_ARGS__()括起来。

编辑:在尝试在实际项目中使用它们后,我更新了上述宏定义。我还添加了一些其他相关的宏定义,可能会有用(RECURSE_FIRST(仅通过第一个条目),RECURSE_LATTER(仅通过第一个条目之后的条目),以及它们的_ESCAPE版本,以及RECURSE_SPLIT(通过第一个条目,对其应用VARIADIC_FUNC_FIRST()宏定义,仅通过后面的条目,对其应用VARIADIC_FUNC()宏定义,再次通过第一个条目,对其应用VARIADIC_FUNC_END()宏定义,并将所有这些连接在一起...):

//#define ESCAPE_PAREN(...) __VA_ARGS__

#define RESOLVE_A(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__) RESOLVE_B)
#define RESOLVE_B(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__) RESOLVE_A)
#define RECURSE(...) RESOLVE_A __VA_ARGS__
//#define RECURSE_ESCAPE(...) RECURSE(ESCAPE_PAREN __VA_ARGS__)

#define RESOLVE_FIRST(...) __VA_OPT__(VARIADIC_FUNC(__VA_ARGS__) DISCARD_A)
#define RESOLVE_LATTER(...) RESOLVE_B
#define DISCARD_A(...) __VA_OPT__(DISCARD_B)
#define DISCARD_B(...) __VA_OPT__(DISCARD_A)
#define RECURSE_FIRST(...) RESOLVE_FIRST __VA_ARGS__ ()
#define RECURSE_LATTER(...) RESOLVE_LATTER __VA_ARGS__ ()

#define RESOLVE_SPLIT_FIRST(...) __VA_OPT__(VARIADIC_FUNC_FIRST(__VA_ARGS__) DISCARD_A)
#define RESOLVE_SPLIT_END(...) __VA_OPT__(VARIADIC_FUNC_END(__VA_ARGS__) DISCARD_A)
#define RECURSE_SPLIT(...) RESOLVE_SPLIT_FIRST __VA_ARGS__ () RESOLVE_LATTER __VA_ARGS__ () RESOLVE_SPLIT_END __VA_ARGS__ ()

我发现在编写宏时,使用这些语言元素要简单得多,不会有太多麻烦和限制...... 它们当然可以像上面一样工作,您可以使用以下形式的 X 宏:

MyXMacro((a) (b) (57) (32))
MyXMacro((c) (d) (49) (32))

另请参阅:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接