是否可以遍历可变参数宏中的参数?


72

我想知道是否有可能遍历传递给C99中可变参数宏的参数或使用任何GCC扩展?

例如,是否可以编写一个通用宏,该宏将结构及其字段作为参数传递并打印结构中每个字段的偏移量?

像这样:

构造一个{
    诠释
    int b;
    int c;
};

/ * PRN_STRUCT_OFFSETS将打印每个字段的偏移量 
   内部结构作为第一个参数传递。
* /

int main(int argc,char * argv [])
{
    PRN_STRUCT_OFFSETS(结构a,a,b,c);

    返回0;
}

Answers:


69

这是我今天的作业,它基于宏技巧,今天我特别了解__VA_NARG__Laurent Deniau发明的内容。无论如何,以下示例代码为清楚起见最多可处理8个字段。如果需要更多,只需通过复制来扩展代码(这是因为预处理器不具有递归功能,因为它只读取一次文件)。

输出:


编辑:这是一个略有不同的版本,试图变得更加通用。FOR_EACH(what, ...)宏适用what于变量参数列表中的所有其他参数。

因此,您只需要定义一个采用单个参数的宏,如下所示:

这将应用于列表中的每个参数。因此,对于您的典型示例,您需要稍微修改一下,但仍然很简洁:

您可以像这样应用它:

最后,一个完整的示例程序:


2
整齐。我想知道是否可以通过将VA_ARGS传递到另一个具有命名参数以捕获其中一个VA_ARGS的宏来拆分VA_ARGS,所以我喜欢这个答案。糟糕的CPP会使您为每个计数编写宏,而不是允许递归扩展并在没有参数的情况下执行其他操作。我不知道我是否会包含这么大的宏集合,除非它将在某个地方保存很多代码。好吧,也许是我自己在开发过程中使用的。。。
彼得·科德斯

1
这是格里高利的好戏。我在谷歌搜索时偶然发现了VA_NARG帖子,但不知道(或者是无知的)您可以使用它来基于参数数量来构建调度程序宏。GMan,您的就是我最初采用的方法。Phillipe,X-Macros是一种有趣的方法。感谢大家的答复。
vshenoy

2
我已经在stackoverflow.com/questions/2751870/…中看到了双字符串化的工作原理,但是为什么STRINGIZE和CONCATENATE三个调用很深?
Henk

Henk>实际上,我不记得为什么,它已经存在于我的代码库中已有很长时间了。1)某些令人毛骨悚然的编译器需要它2)要么这是我的错:)
Gregory Pakosz 2011年

它肯定与旧的代码战士编译器或msvc ++ 6有关
Gregory Pakosz 2011年

50

冒着获得考古学家徽章的风险,我认为使用重载论据数量上的宏技术可以对上述格雷戈里的答案进行较小的改进

使用foo.h:

cpp foo.h生成:


3
我需要将的定义更改GET_MACROGET_MACRO(__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1,)(action,__VA_ARGS__)。请注意多余的逗号。没有这个,我将宏应用于具有单个参数的列表warning: ISO C99 requires rest arguments to be used。除此之外,宏很棒!
wyer33,2015年

太好了,您值得拥有考古学家的徽章!
pooya '16

4
对于尝试使用msvc进行尝试的用户(此处为2015),由于msvc不会扩展__VA_ARGS__为多个参数,即__VA_ARGS__is是a,b,cFOO(X, __VA_ARGS__)FOO(X, (a,b,c))不是,因此需要稍作修改FOO(X, a, b, c)。解决方案在这里:stackoverflow.com/questions/5134523/…-简而言之,GET_MACRO(__VA_ARGS__, ...)(action,__VA_ARGS__) 需要重新编写,EXPAND(GET_MACRO(__VA_ARGS__, ...)(action,__VA_ARGS__))并且还FE_X需要包装在EXPAND(...)宏中。
GaspardP '16

2
GET_MACRO调用中,请在后面添加一个逗号,FE_1以防止Wgnu-zero-variadic-macro-arguments仅用1项调用该宏来进行迭代。
Etan

17

如果使用X-Macros描述您的结构,则可以编写一个函数或宏来遍历该结构的所有字段并打印其偏移量。


2
它只会混淆结构和打印偏移量的函数的声明,而不会混淆X()宏的作用。但是优点是,当您必须向结构中添加新字段时,只有一个地方可以修改,即X_FIELDS宏。重新编译,print_offset()函数将打印新字段的偏移量。不要重复自己!
philant

并且仅在结构是您自己的并且您愿意使它的定义混乱(恕我直言)时才适用
Gregory Pakosz

3
我只是想在需要枚举并按名称访问枚举元素的情况下使用此方法。它确实使代码变得晦涩难懂,但可以使最终用户获得更好的体验,并且没有任何限制。
达里奥

我做了类似的事情,但除了最后一个X之外,最后都使用了无参数的Y宏,以考虑到在某些情况下必须在项目之间使用分隔符,而不是在每个项目之后使用终结符。
supercat 2015年

我不知道这种技术有一个名字和一个维基百科页面!我经常使用X-macros!
印第安纳·克尼克,

8

Gregory Pakosz的解决方案效果很好。但是我有两个小问题:

  1. 使用pedantic选项进行编译时,出现了警告:“ ISO99要求使用rest参数”。这是由第一个FOR_EACH_1宏中的variad参数引起的。删除这些警告并将呼叫更改为FOR_EACH_2中的FOR_EACH_1即可删除此警告。

  2. 由于我以一种非常通用的方式使用它,因此有时我不得不只用1个参数调用repeat宏。(我知道重复一个项目没有意义;)。幸运的是,解决该问题的方法非常简单。只需从FOR_EACH宏中删除x参数。

这里列出了两个更改的完整列表:


5

也许使用varargs作为数组初始化器,并遍历countof(array)?即sizeof(array)/ sizeof(array [0])。该阵列可能是C99匿名阵列。

我想不出另一种方法来遍历宏的var-args,因为我不知道如何对每个var-arg元素的文本执行任何操作。var-arg部分也可能是其中包含逗号的单个参数,因为您可以使用CPP,AFIAK对它进行全部处理。

但是这是我遍历var-args的想法:


1
抱歉,我看不到此代码片段如何回答问题。首先,countof虽然您在第一段中给出了定义,但代码缺少定义。那应该是int ar_[]。最后,它只有在使用带有可变参数列表的int参数调用宏时才有效;像这样MACRO(stdout, "%d", 1, 2, 3)
Gregory Pakosz,2009年

1
显然,您需要调整宏以适合您的情况。您可以将类型设为宏参数之一。不过,感谢您发现缺失的[]。
彼得·科德斯

2
仍然,使用此数组意味着两件事:通过变量参数列表传递的所有参数都int必须具有相同的类型(在您的情况下),并且必须具有公共副本构造函数
Gregory Pakosz,2009年

是的,对此有主要限制。我并不是说这是一个好的或普遍有用的答案!不过,这可能是您在C99 / GNU C中可以做的最好的事情。在C ++中,您能代替模板做些什么吗?
彼得·科德斯


0

对于标准C,这是我能想到的最好的方法:


实际上,我误解了问题,我以为他想输出,而不是偏移量,但事实并非如此;)
catchmeifyoutry


-1

我将此添加为另一个答案。这是使用C ++ 0x并用g ++ 4.5.0编译的尝试

程序打印

123

但是,使用这种方法,int在上面的示例中,传递给lambda表达式的所有参数都必须具有相同的类型。但是,lambda允许您捕获变量,例如:

输出:


宏观方法如何?
沼泽

1
不是。对于使用C ++ 11并愿意避免使用宏的用户来说,这是一种替代方法。宏解决方案是公认的答案。
格雷戈里·帕科斯

另外,当时我认为问题中没有C标签
Gregory Pakosz

如果我想使用宏,STD(cout, endl, cin)那么它将扩展为using std::cout; using std::endl; using std::cin;,则看不到如何通过模板来实现这种宏扩展。
Eljay

-1

如果您的目标是Objective-C……请在Github上查看AWESOME KSVarArgs

KSVarArgs是一组宏,旨在使在Objective-C中更轻松地处理变量参数。所有宏都假定varargs列表仅包含Objective-C对象或类似对象的结构(可分配给ID类型)。基本宏ksva_iterate_list()遍历变量参数,为每个参数调用一个块,直到遇到终止nil。其他宏是为了方便在转换为通用集合时使用。

用法示例:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.