【问题标题】:Overloading Macro on Number of Arguments在参数数量上重载宏
【发布时间】:2012-07-30 11:51:33
【问题描述】:

我有两个宏 FOO2FOO3

#define FOO2(x,y) ...
#define FOO3(x,y,z) ...

我想定义一个新的宏FOO如下:

#define FOO(x,y) FOO2(x,y)
#define FOO(x,y,z) FOO3(x,y,z)

但这不起作用,因为宏不会在参数数量上超载。

在不修改FOO2FOO3的情况下,有没有办法定义一个宏FOO(使用__VA_ARGS__或其他方式)以获得将FOO(x,y)调度到FOO2和@987654336的相同效果@到FOO3

【问题讨论】:

标签: c macros c-preprocessor


【解决方案1】:

简单如下:

#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2)(__VA_ARGS__)

如果你有这些宏:

FOO(World, !)         # expands to FOO2(World, !)
FOO(foo,bar,baz)      # expands to FOO3(foo,bar,baz)

如果你想要第四个:

#define GET_MACRO(_1,_2,_3,_4,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO4, FOO3, FOO2)(__VA_ARGS__)

FOO(a,b,c,d)          # expeands to FOO4(a,b,c,d)

当然,如果您定义了FOO2FOO3FOO4,则输出将替换为已定义宏的输出。

【讨论】:

  • @Uroc327 可以在列表中添加 0 参数宏,请参阅我的回答。
  • 不适用于 Microsoft Visual Studio 2010,VA_ARGS 似乎已扩展为单个宏参数。
  • 找到 this answer 使其在 MSVC 2010 下工作。
  • 如果有人对如何使用@Étienne 链接中提到的EXPAND 感到困惑,您基本上可以在GET_MACRO 上调用它,就像#define FOO(...) EXPAND(GET_MACRO(__VA_ARGS__, FOO3, FOO2, FOO1)(__VA_ARGS__)) 一样,它应该扩展到正确数量的参数msvc.
  • 请注意,在 C++11 上,您会收到警告:ISO C++11 requires at least one argument for the "..." in a variadic macro。要解决此问题,请在 FOO(...) 定义的最后一个参数之后添加一个未使用的参数(甚至只是一个逗号):#define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2, UNUSED)(__VA_ARGS__) (See it run on Coliru)。
【解决方案2】:

要添加到netcoder's answer,实际上您可以在 GCC ##__VA_ARGS__ 扩展的帮助下使用 0 参数宏来执行此操作:

#define GET_MACRO(_0, _1, _2, NAME, ...) NAME
#define FOO(...) GET_MACRO(_0, ##__VA_ARGS__, FOO2, FOO1, FOO0)(__VA_ARGS__)

【讨论】:

  • 不做#define FOO0 _Pragma("error FOO0 not allowed")是否可以允许FOO1和FOO2但不允许FOO0?
  • FOO0 不能在 qt + mingw32 中工作,调用 FOO0 将调用 FOO1
  • 非常有前途和简单。但不适用于带有 -std=c++11...:-( 的 FOO0
  • 如果您在 C 中执行此操作并尝试使用 -std=c99-std=c11,则会出现同样的问题。您需要改用-std=gnu99-std=gnu11
  • 看来用_0 __VA_OPT__(,) __VA_ARGS__ 替换_0, ##__VA_ARGS__ 是新的方法。
【解决方案3】:

这是一个更通用的解决方案:

// get number of arguments with __NARG__
#define __NARG__(...)  __NARG_I_(__VA_ARGS__,__RSEQ_N())
#define __NARG_I_(...) __ARG_N(__VA_ARGS__)
#define __ARG_N( \
      _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
     _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
     _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
     _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
     _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
     _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
     _61,_62,_63,N,...) N
#define __RSEQ_N() \
     63,62,61,60,                   \
     59,58,57,56,55,54,53,52,51,50, \
     49,48,47,46,45,44,43,42,41,40, \
     39,38,37,36,35,34,33,32,31,30, \
     29,28,27,26,25,24,23,22,21,20, \
     19,18,17,16,15,14,13,12,11,10, \
     9,8,7,6,5,4,3,2,1,0

// general definition for any function name
#define _VFUNC_(name, n) name##n
#define _VFUNC(name, n) _VFUNC_(name, n)
#define VFUNC(func, ...) _VFUNC(func, __NARG__(__VA_ARGS__)) (__VA_ARGS__)

// definition for FOO
#define FOO(...) VFUNC(FOO, __VA_ARGS__)

定义你的功能:

#define FOO2(x, y) ((x) + (y))
#define FOO3(x, y, z) ((x) + (y) + (z))

// it also works with C functions:
int FOO4(int a, int b, int c, int d) { return a + b + c + d; }

现在您可以将FOO 与 2、3 和 4 个参数一起使用:

FOO(42, 42) // will use makro function FOO2
FOO(42, 42, 42) // will use makro function FOO3
FOO(42, 42, 42, 42) // will call FOO4 function

限制

  • 最多只能有 63 个参数(但可扩展)
  • 只能在 GCC 中实现无参数功能

想法

将其用于默认参数:

#define func(...) VFUNC(func, __VA_ARGS__)
#define func2(a, b) func4(a, b, NULL, NULL)
#define func3(a, b, c) func4(a, b, c, NULL)

// real function:
int func4(int a, int b, void* c, void* d) { /* ... */ }

将它用于可能具有无限个参数的函数:

#define SUM(...) VFUNC(SUM, __VA_ARGS__)
#define SUM2(a, b) ((a) + (b))
#define SUM3(a, b, c) ((a) + (b) + (c))
#define SUM4(a, b, c) ((a) + (b) + (c) + (d))
// ...

PS:__NARG__ 是从 Laurent Deniau 和 Roland Illig 复制而来:https://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5?pli=1

【讨论】:

  • __NARG_I_ 似乎完全没有必要和多余。它只是增加了一个额外的步骤和混乱。我建议将其完全删除,并将 __NARG__ 定义为:#define __NARG__(...) __ARG_N(__VA_ARGS__,__RSEQ_N())
  • 或者这会以某种方式破坏预处理?我错过了什么吗?
  • _VFUNC_相同:删除即可。然后,将_VFUNC 定义为:#define _VFUNC(name, n) name##n 而不是#define _VFUNC(name, n) _VFUNC_(name, n)
【解决方案4】:

我自己只是在研究这个,我遇到了这个here。作者通过宏添加了对 C 函数的默认参数支持。

我将尝试简要总结这篇文章。基本上,您需要定义一个可以计算参数的宏。此宏将返回 2、1、0 或它可以支持的任何参数范围。例如:

#define _ARG2(_0, _1, _2, ...) _2
#define NARG2(...) _ARG2(__VA_ARGS__, 2, 1, 0)

这样,您需要创建另一个宏,该宏接受可变数量的参数、计算参数并调用适当的宏。我采用了您的示例宏并将其与文章的示例相结合。我有 FOO1 调用函数 a() 和 FOO2 调用函数 a 参数 b (显然,我在这里假设 C++,但你可以将宏更改为任何内容)。

#define FOO1(a) a();
#define FOO2(a,b) a(b);

#define _ARG2(_0, _1, _2, ...) _2
#define NARG2(...) _ARG2(__VA_ARGS__, 2, 1, 0)

#define _ONE_OR_TWO_ARGS_1(a) FOO1(a)
#define _ONE_OR_TWO_ARGS_2(a, b) FOO2(a,b)

#define __ONE_OR_TWO_ARGS(N, ...) _ONE_OR_TWO_ARGS_ ## N (__VA_ARGS__)
#define _ONE_OR_TWO_ARGS(N, ...) __ONE_OR_TWO_ARGS(N, __VA_ARGS__)

#define FOO(...) _ONE_OR_TWO_ARGS(NARG2(__VA_ARGS__), __VA_ARGS__)

如果你有

FOO(a)
FOO(a,b)

预处理器将其扩展为

a();
a(b);

我肯定会阅读我链接的文章。这是非常有用的,他提到 NARG2 不适用于空参数。他跟进了here

【讨论】:

    【解决方案5】:

    这里是the answer above 的更紧凑版本。举个例子。

    #include <iostream>
    using namespace std;
    
    #define OVERLOADED_MACRO(M, ...) _OVR(M, _COUNT_ARGS(__VA_ARGS__)) (__VA_ARGS__)
    #define _OVR(macroName, number_of_args)   _OVR_EXPAND(macroName, number_of_args)
    #define _OVR_EXPAND(macroName, number_of_args)    macroName##number_of_args
    
    #define _COUNT_ARGS(...)  _ARG_PATTERN_MATCH(__VA_ARGS__, 9,8,7,6,5,4,3,2,1)
    #define _ARG_PATTERN_MATCH(_1,_2,_3,_4,_5,_6,_7,_8,_9, N, ...)   N
    
    
    //Example:
    #define ff(...)     OVERLOADED_MACRO(ff, __VA_ARGS__)
    #define ii(...)     OVERLOADED_MACRO(ii, __VA_ARGS__)
    
    #define ff3(c, a, b) for (int c = int(a); c < int(b); ++c)
    #define ff2(c, b)   ff3(c, 0, b)
    
    #define ii2(a, b)   ff3(i, a, b)
    #define ii1(n)      ii2(0, n)
    
    
    int main() {
        ff (counter, 3, 5)
            cout << "counter = " << counter << endl;
        ff (abc, 4)
            cout << "abc = " << abc << endl;
        ii (3)
            cout << "i = " << i << endl;
        ii (100, 103)
            cout << "i = " << i << endl;
    
    
        return 0;
    }
    

    运行:

    User@Table 13:06:16 /c/T
    $ g++ test_overloaded_macros.cpp 
    
    User@Table 13:16:26 /c/T
    $ ./a.exe
    counter = 3
    counter = 4
    abc = 0
    abc = 1
    abc = 2
    abc = 3
    i = 0
    i = 1
    i = 2
    i = 100
    i = 101
    i = 102
    

    请注意,同时拥有_OVR_OVR_EXPAND 可能看起来多余,但预处理器有必要扩展_COUNT_ARGS(__VA_ARGS__) 部分,否则将被视为字符串。

    【讨论】:

    • 我喜欢这个解决方案。可以修改它以处理带零参数的重载宏吗?
    【解决方案6】:

    这是从 Evgeni Sergeev 的回答中衍生出来的。这个也支持零参数重载

    我使用 GCC 和 MinGW 对此进行了测试。它应该适用于新旧版本的 C++。请注意,我不能保证它适用于 MSVC...但是通过一些调整,我相信它也可以与它一起使用。

    我还对其进行了格式化以粘贴到一个头文件中(我称之为macroutil.h)。如果你这样做了,你可以在任何你需要的特性中包含这个标题,而不是查看实现中涉及的肮脏。

    #ifndef MACROUTIL_H
    #define MACROUTIL_H
    
    //-----------------------------------------------------------------------------
    // OVERLOADED_MACRO
    //
    // used to create other macros with overloaded argument lists
    //
    // Example Use:
    // #define myMacro(...) OVERLOADED_MACRO( myMacro, __VA_ARGS__ )
    // #define myMacro0() someFunc()
    // #define myMacro1( arg1 ) someFunc( arg1 )
    // #define myMacro2( arg1, arg2 ) someFunc( arg1, arg2 )
    //
    // myMacro();
    // myMacro(1);
    // myMacro(1,2);
    //
    // Note the numerical suffix on the macro names,
    // which indicates the number of arguments.
    // That is the REQUIRED naming convention for your macros.
    //
    //-----------------------------------------------------------------------------
    
    // OVERLOADED_MACRO
    // derived from: https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments
    // replaced use of _COUNT_ARGS macro with VA_NUM_ARGS defined below
    // to support of zero argument overloads
    #define OVERLOADED_MACRO(M, ...) _OVR(M, VA_NUM_ARGS(__VA_ARGS__)) (__VA_ARGS__)
    #define _OVR(macroName, number_of_args)   _OVR_EXPAND(macroName, number_of_args)
    #define _OVR_EXPAND(macroName, number_of_args)    macroName##number_of_args
    //#define _COUNT_ARGS(...)  _ARG_PATTERN_MATCH(__VA_ARGS__, 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)
    #define _ARG_PATTERN_MATCH(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15, N, ...)   N
    
    // VA_NUM_ARGS
    // copied from comments section of:
    // http://efesx.com/2010/07/17/variadic-macro-to-count-number-of-arguments/
    // which itself was derived from:
    // https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
    #define _ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
    #define HAS_COMMA(...) _ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
    #define HAS_NO_COMMA(...) _ARG16(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
    #define _TRIGGER_PARENTHESIS_(...) ,
    
    #define HAS_ZERO_OR_ONE_ARGS(...) \
        _HAS_ZERO_OR_ONE_ARGS( \
        /* test if there is just one argument, eventually an empty one */ \
        HAS_COMMA(__VA_ARGS__), \
        /* test if _TRIGGER_PARENTHESIS_ together with the argument adds a comma */ \
        HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__), \
        /* test if the argument together with a parenthesis adds a comma */ \
        HAS_COMMA(__VA_ARGS__ (~)), \
        /* test if placing it between _TRIGGER_PARENTHESIS_ and the parenthesis adds a comma */ \
        HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (~)) \
        )
    
    #define PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
    #define _HAS_ZERO_OR_ONE_ARGS(_0, _1, _2, _3) HAS_NO_COMMA(PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3))
    #define _IS_EMPTY_CASE_0001 ,
    
    #define _VA0(...) HAS_ZERO_OR_ONE_ARGS(__VA_ARGS__)
    #define _VA1(...) HAS_ZERO_OR_ONE_ARGS(__VA_ARGS__)
    #define _VA2(...) 2
    #define _VA3(...) 3
    #define _VA4(...) 4
    #define _VA5(...) 5
    #define _VA6(...) 6
    #define _VA7(...) 7
    #define _VA8(...) 8
    #define _VA9(...) 9
    #define _VA10(...) 10
    #define _VA11(...) 11
    #define _VA12(...) 12
    #define _VA13(...) 13
    #define _VA14(...) 14
    #define _VA15(...) 15
    #define _VA16(...) 16
    
    #define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, PP_RSEQ_N(__VA_ARGS__) )
    #define VA_NUM_ARGS_IMPL(...) VA_NUM_ARGS_N(__VA_ARGS__)
    
    #define VA_NUM_ARGS_N( \
        _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
        _11,_12,_13,_14,_15,_16,N,...) N
    
    #define PP_RSEQ_N(...) \
        _VA16(__VA_ARGS__),_VA15(__VA_ARGS__),_VA14(__VA_ARGS__),_VA13(__VA_ARGS__), \
        _VA12(__VA_ARGS__),_VA11(__VA_ARGS__),_VA10(__VA_ARGS__), _VA9(__VA_ARGS__), \
        _VA8(__VA_ARGS__),_VA7(__VA_ARGS__),_VA6(__VA_ARGS__),_VA5(__VA_ARGS__), \
        _VA4(__VA_ARGS__),_VA3(__VA_ARGS__),_VA2(__VA_ARGS__),_VA1(__VA_ARGS__), \
        _VA0(__VA_ARGS__)
    
    //-----------------------------------------------------------------------------
    
    #endif // MACROUTIL_H
    

    【讨论】:

      【解决方案7】:

      也许你可以用这个宏来count the number of arguments

      #define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
      #define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N
      

      【讨论】:

        【解决方案8】:

        这似乎在 GCC、Clang 和 MSVC 上运行良好。这是这里一些答案的清理版本

        #define _my_BUGFX(x) x
        
        #define _my_NARG2(...) _my_BUGFX(_my_NARG1(__VA_ARGS__,_my_RSEQN()))
        #define _my_NARG1(...) _my_BUGFX(_my_ARGSN(__VA_ARGS__))
        #define _my_ARGSN(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,N,...) N
        #define _my_RSEQN() 10,9,8,7,6,5,4,3,2,1,0
        
        #define _my_FUNC2(name,n) name ## n
        #define _my_FUNC1(name,n) _my_FUNC2(name,n)
        #define GET_MACRO(func,...) _my_FUNC1(func,_my_BUGFX(_my_NARG2(__VA_ARGS__))) (__VA_ARGS__)
        
        #define FOO(...) GET_MACRO(FOO,__VA_ARGS__)
        

        【讨论】:

        • @RianQuinn 如何调整此宏以使其使用零参数#define func0() foo?不幸的是,当前版本无法处理这种情况。
        【解决方案9】:

        基于 @netcoder's answer@vexe's suggestion 关于 Visual Studio 编译器支持的信息,我发现这段代码在各种平台上运行良好:

        #define FOO1(a) func1(a)
        #define FOO2(a, b) func2(a, b)
        #define FOO3(a, b, c) func3(a, b, c)
        
        #define EXPAND(x) x
        #define GET_MACRO(_1, _2, _3, NAME, ...) NAME
        #define FOO(...) EXPAND(GET_MACRO(__VA_ARGS__, FOO3, FOO2, FOO1)(__VA_ARGS__))
        

        ,其中func1()func2()func3() 只是接受不同数量参数的普通函数。

        【讨论】:

          猜你喜欢
          • 2013-05-16
          • 2014-10-30
          • 2023-04-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-06
          相关资源
          最近更新 更多