【问题标题】:Variable arguments inside a macro宏内的变量参数
【发布时间】:2018-05-20 08:33:09
【问题描述】:

我有两个函数 foo1(a,b) & foo2(a,b,c) 和一个宏

#define add(a,b) foo(a,b)

我需要重新定义宏来完成,

1.如果用2个参数调用add(),则调用foo1

  1. 如果使用 3 个参数调用 add(),则调用 foo2

我是选项 VA_ARGS 的新手。我该怎么做呢

【问题讨论】:

  • 为什么不能将add 重新编码为接受变量参数列表的函数?
  • sane 解决方案:一个函数type add (type a, type b, type c);,其中c 是可选的,可以设置为NULL 或类似的值。可变参数宏通常是通往疯狂的道路。

标签: c variables macros arguments


【解决方案1】:

如果你只是想区分两个功能,下面的工作:

#define ADD(_1, _2, _3, X, ...) X
#define add(...) ADD(__VA_ARGS__, add3, add2, 0)(__VA_ARGS__)

辅助宏ADD 总是选择第四个参数:

add(a, b)    --> ADD(a, b, add3, add2, 0)    --> add2
add(a, b, c) --> ADD(a, b, c, add3, add2, 0) --> add3

缺点是当您不向函数提供两个或三个参数时,您会收到非常神秘的错误消息。

与可变参数函数相比,优势在于您获得了类型安全性。例如,如果您的函数在doubles 上运行,您仍然可以说add(1, 2),并且整数参数将转换为doubles。可变参数函数需要一些关于实际参数数量的额外信息,所以这不是一个可行的解决方案,除非你在函数中指定被加数的数量。

附录:我更改了add 宏,使其不会将空的可变参数列表传递给ADD。一些编译器允许空列表,但它不是标准 C。

【讨论】:

  • 你的文章比我的文章更易读,我必须说。确定+1
  • 我不相信#define ADD(_1, _2, _3, X, ...) X 是标准C,你必须在某个地方烧掉__VA_ARGS__。不要在启用某些 gcc 扩展的情况下进行编译。
  • 是的,刚刚在标准模式下检查了 GCC。此代码不会编译。非常聪明的技巧,但不幸的是,它不是有效的 C。
  • @Lundin:哦。我不知道可变参数宏必须在某处使用__VA_ARGS__。我在标准模式下收到警告。奇怪的是,我可以通过在add 中的add2 之后添加一个虚拟参数来消除它,这很可疑,因为它不会改变我忽略ADD 中的可变参数的事实。不过,它使可变参数 args 成为非空列表。
  • @Lundin:嗯。我使用较新的 gcc 收到不同的错误消息。似乎不使用可变参数是合法的,但传递零可变参数是不合法的。如果这是真的,添加虚拟参数是有效的。我已经尝试过使用公认过时的 Visual Studio 版本,但它根本无法编译。 (不过,这并没有说明标准合规性。)
【解决方案2】:

usual trick for counting arguments 可能适用于此:

#define ADD_EXPAND(...) \
        ADD_EXPAND_(__VA_ARGS__, EXPAND_ADD_FUNCS())

#define ADD_EXPAND_(...) \
        EXPAND_ADD_SEL_FUNC(__VA_ARGS__)

#define EXPAND_ADD_SEL_FUNC(first_, second_, third_, func, ...) func

#define EXPAND_ADD_FUNCS() foo2, foo, dummy

#define add(...) ADD_EXPAND(__VA_ARGS__)(__VA_ARGS__)

一旦你通过样板,它基本上只需将所有参数放在一行中,然后是函数标记,然后查看哪个函数突出。这就是EXPAND_ADD_SEL_FUNC 所做的。

你可以看到它live on coliru

但我会重申我们在 cmets 中告诉你的内容。这可能是对适当功能的低于标准的解决方案。我还没有彻底测试过,所以很容易损坏。使用风险自负。

【讨论】:

    【解决方案3】:

    如果您必须使用可变参数宏,那么这里有一个技巧。

    #define add(...) _Generic ( &(int[]){__VA_ARGS__}, \
                                int(*)[2]: add2,       \
                                int(*)[3]: add3) (__VA_ARGS__)
    
    • 让宏创建一个复合文字数组。该数组的大小取决于参数的数量。
    • 获取复合字面量的地址,获取数组指针类型。
    • _Generic检查你得到的类型,然后根据它调用正确的函数。

    这是 100% 标准的 C 语言,而且类型安全。


    演示:

    #include <stdio.h>
    
    
    #define add(...) _Generic ( &(int[]){__VA_ARGS__}, \
                                int(*)[2]: add2,       \
                                int(*)[3]: add3) (__VA_ARGS__)
    
    int add2 (int a, int b);
    int add3 (int a, int b, int c);
    
    int main (void)
    {
      printf("%d\n", add(1, 2));
      printf("%d\n", add(1, 2, 3));
    //printf("%d\n", add(1, 2, 3, 4)); Compiler error for this.
    }
    
    
    int add2 (int a, int b)
    {
      return a + b;
    }
    
    int add3 (int a, int b, int c)
    {
      return a + b + c;
    }
    

    【讨论】:

    • 此解决方案有一些小缺点,即函数参数必须与int 的赋值兼容,即使它们是,您也可能会收到奇怪的警告。对于后者,使用_Complex long double 而不是int 将允许捕获所有算术类型。
    • @JensGustedt 这是有意的,因为可变参数宏不存在类型安全性。只有通过兼容/隐式可转换类型时,代码才会编译。字符类型、浮点等都可以使用,但不接受聚合等上的指针类型。获得诊断是一件好事,隐式和静默转换是一件坏事。
    • 这是 100% 标准 C11。我认为这不会传递给 C90 或 C99 编译器。
    • @Cyan 只有一个 C 标准。这就是标准化的全部意义所在。该标准目前是 ISO 9899:2018。撰写本文时,C 标准为 ISO 9899:2011。 ISO 9899:1999 在十多年前被撤销。
    • 语义烟幕。这对于必须编写可移植代码并且必须注意给定表达式是否可以在 C90 或 C99 编译器上工作的人几乎没有帮助。您的建议仅适用于更新的 C11 编译器是一个相当有用的信息,应该向读者提及。相比之下,Oehm 在下面的答案与所有​​支持可变参数宏的编译器兼容,从 C99 开始(以及我所知道的几乎所有 C90)。值得强调的区别。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-25
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 2014-10-01
    • 2016-11-08
    相关资源
    最近更新 更多