【问题标题】:Can macros be overloaded by number of arguments?可以通过参数数量重载宏吗?
【发布时间】:2013-05-16 23:54:12
【问题描述】:

this 是如何工作的?如何实现 C99/C++11 可变参数宏以仅根据为其提供多少参数来扩展为不同的事物?

【问题讨论】:

  • 不回答它是如何工作的,但如果有人只需要一个实现:boost preprocessor

标签: c++ c overloading c-preprocessor variadic-macros


【解决方案1】:

我扩展了 Potatowatter 的解决方案,以避免在使用 gcc 的编译器开关 -pedantic 时出现 iso c99 requires rest arguments to be used 问题。

图书馆

#define NUM_ARGS_(_1, _2, _3, _4, _5, _6, _7, _8, TOTAL, ...) TOTAL
#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define CONCATE_(X, Y) X##Y
#define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER)
#define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS (__VA_ARGS__))(__VA_ARGS__)

自定义

#define MY_OVERLOADED(...) VA_MACRO(MY_OVERLOADED, void, void, __VA_ARGS__)
#define MY_OVERLOADED0(s, t) MacroTest()
#define MY_OVERLOADED1(s, t, a) MacroTest( a)
#define MY_OVERLOADED2(s, t, a, b) MacroTest(a, b)
#define MY_OVERLOADED3(s, t, a, b, c) MacroTest(a, b, c)

用法

MY_OVERLOADED();
MY_OVERLOADED(1);
MY_OVERLOADED(11, 22);
MY_OVERLOADED(111, 222, 333);

【讨论】:

    【解决方案2】:

    以下是对Potatoswatter's answer 的改进,它可以区分零和一个参数。

    简而言之,当__VA_ARGS__ 为空时,VA_SIZE 宏内部的EXPAND __VA_ARGS__ () 变为EXPAND () 并替换为6 个逗号。因此,VA_SIZE... 变为 COMPOSE( GET_COUNT, (,,,,,, , 0, 6, 5, 4, 3, 2, 1) ),然后变为 GET_COUNT (,,,,,, , 0, 6, 5, 4, 3, 2, 1) 并返回 0。

    另一方面,当__VA_ARGS__int, 5 时,EXPAND __VA_ARGS__ () 变为EXPAND int, 5 ()。因此,VA_SIZE... 变为 COMPOSE( GET_COUNT, (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) ),后者变为 GET_COUNT (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) 并返回 2,如 Potatoswatter 的回答中所述。

    我从Jason Dang's answer 那里得到了EXPAND 的想法。

    库代码:

    #define CAT( A, B ) A ## B
    #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )
    #define COMPOSE( NAME, ARGS ) NAME ARGS
    
    #define GET_COUNT( _0, _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT
    #define EXPAND() ,,,,,, // 6 commas (or 7 empty tokens)
    #define VA_SIZE( ... ) COMPOSE( GET_COUNT, (EXPAND __VA_ARGS__ (), 0, 6, 5, 4, 3, 2, 1) )
    
    #define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__)
    

    用法:

    #define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ )
    
    #define MY_OVERLOADED_0( ) meh()
    #define MY_OVERLOADED_1( X ) foo< X >
    #define MY_OVERLOADED_2( X, Y ) bar< X >( Y )
    #define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z()
    
    MY_OVERLOADED()                // meh()
    MY_OVERLOADED(bool)            // foo< bool >
    MY_OVERLOADED(int, 5)          // bar< int >( 5 )
    MY_OVERLOADED(me, double, now) // bang_me< double >.now()
    

    【讨论】:

    • @CaptJak 不是我的错。 OP想出了这个例子。 :D
    • 这给了我:iso c99 requires rest arguments to be used。由于项目原因,我必须使用 gcc 的 -pedantic 开关。
    【解决方案3】:

    (编辑:见最后一个现成的解决方案。)

    要获得一个重载的宏,首先我们需要一个在多个实现之间进行选择的宏。这部分不使用可变参数宏。然后一个通常计算其参数的可变参数宏产生一个选择器。将参数计数插入调度程序会产生一个重载的宏。

    警告:此系统无法区分零个参数和一个参数,因为 没有参数和单个空参数之间没有区别。它们看起来都像MACRO()


    要在实现之间进行选择,请将宏连接运算符与一系列类似函数的宏一起使用。

    #define select( selector, ... ) impl ## _ ## selector( __VA_ARGS__ )
    #define impl_1() meh
    #define impl_2( abc, xyz ) # abc "wizza" xyz()
    //etc
    
    // usage: select( 1 ) => impl_1() => meh
    //        select( 2, huz, bar ) => impl_2( huzza, bar ) => "huz" "wizza" bar()
    

    因为## 运算符禁止对其参数进行宏扩展,所以最好将其包装在另一个宏中。

    #define CAT( A, B ) A ## B
    #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )
    

    要计算参数,请使用__VA_ARGS__ 像这样转换参数(这是聪明的部分):

    #define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT
    #define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 )
    

    库代码:

    #define CAT( A, B ) A ## B
    #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )
    
    #define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT
    #define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 )
    
    #define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__)
    

    用法:

    #define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ )
    #define MY_OVERLOADED_1( X ) foo< X >
    #define MY_OVERLOADED_2( X, Y ) bar< X >( Y )
    #define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z()
    

    【讨论】:

    • 很好,我不知道你能可靠地做到这一点。
    • 谢谢,这是一个很好的解释。也许关于“聪明”部分的更多细节会很好。
    • @Oli,假设您将一个参数传递给VA_SIZE。它会扩展为GET_COUNT(x, 6, 5, 4, 3, 2, 1),它会取消x_1,取消6_2,等等,剩下的1COUNT。使用两个参数,再推一个参数,2 变为 COUNT
    • @chris,确实,这很聪明
    • 请注意,在 MSVC 中似乎存在错误。这是一个解决方法:stackoverflow.com/questions/5134523/…
    【解决方案4】:

    虽然已经回答了,但我准备了一个非常简短的版本。希望对您有所帮助。

    实施

    // Variable Argument Macro (VA_MACRO) upto 6 arguments
    #define NUM_ARGS_(_1, _2, _3, _4, _5, _6, TOTAL, ...) TOTAL
    #define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1)
    
    #define CONCATE_(X, Y) X##Y  // Fixed the double '_' from previous code
    #define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER)
    #define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)
    

    定制

    // This is how user may define own set of variadic macros
    #define MY_MACRO(...) VA_MACRO(MY_MACRO, __VA_ARGS__)
    #define MY_MACRO1(_1) "One"
    #define MY_MACRO2(_1, _2) "Two"
    #define MY_MACRO3(_1, _2, _3) "Three"
    

    用法

    // While using those, user needs to use only the main macro
    int main ()
    {
      auto one = MY_MACRO(1);
      auto two = MY_MACRO(1, 2); 
      auto three = MY_MACRO(1, 2, 3); 
    }
    

    【讨论】:

    • 这与我的回答有何不同?我唯一看到的是双下划线,这是一些编译器会标记的真正错误。
    • @Potatoswatter,除了它易于理解、概括、简化并且宏的命名及其参数对于新手来说更清晰之外,没有太大的不同。当我阅读您的答案时,我完全迷失了,并且能够在您的答案的帮助下自己找出“实际”解决方案。关于__,我特意保留了(正如您在评论中看到的那样),因为通常人们会有自己的CONCATECONCATE_ 组合来连接2 个字符串,因此该部分无论如何都会消失。为了便于理解,将“__”视为占位符。
    • 这不是一概而论的。它实际上是相同的代码,但没有附加解释。也许我应该把代码放在第一位,然后把描述放在第二位。 __ 是一个错误;它是为编译器实现者保留的,不允许用户使用。
    • 发现这个答案很有帮助,因为代码更易于阅读。阅读本文,更容易理解@Potatoswatter 的答案。所以也赞成这个答案。
    【解决方案5】:

    我会将此作为对 Potatoswatter 帖子的评论发布,但它太长并且需要代码清单。

    这里有一些 perl 代码,用于生成一组宏,这些宏是重载的宏。

    $ perl -le 'map{
            $arity = $_; map {
                    $ar = 2 + $arity + $_; $arm = $ar - 1; $arlist = join("", map{"A$_, "} 1..$arity); $warlist = "WHAT, $arlist";
                    @li = map {"_$_"} 0..$_; $lis = join(", ", @li); $lim = pop @li; $lims = join(", ", @li);
                    print "#define FEI_${arity}A_$ar($warlist$lis) FEI_${arity}A_$arm($warlist$lims) WHAT($_, $arlist$lim)"
            } 1..3; print ""
    } 0..4'
    

    这是脚本的输出:

    #define FEI_0A_3(WHAT, _0, _1) FEI_0A_2(WHAT, _0) WHAT(1, _1)
    #define FEI_0A_4(WHAT, _0, _1, _2) FEI_0A_3(WHAT, _0, _1) WHAT(2, _2)
    #define FEI_0A_5(WHAT, _0, _1, _2, _3) FEI_0A_4(WHAT, _0, _1, _2) WHAT(3, _3)
    
    #define FEI_1A_4(WHAT, A1, _0, _1) FEI_1A_3(WHAT, A1, _0) WHAT(1, A1, _1)
    #define FEI_1A_5(WHAT, A1, _0, _1, _2) FEI_1A_4(WHAT, A1, _0, _1) WHAT(2, A1, _2)
    #define FEI_1A_6(WHAT, A1, _0, _1, _2, _3) FEI_1A_5(WHAT, A1, _0, _1, _2) WHAT(3, A1, _3)
    
    #define FEI_2A_5(WHAT, A1, A2, _0, _1) FEI_2A_4(WHAT, A1, A2, _0) WHAT(1, A1, A2, _1)
    #define FEI_2A_6(WHAT, A1, A2, _0, _1, _2) FEI_2A_5(WHAT, A1, A2, _0, _1) WHAT(2, A1, A2, _2)
    #define FEI_2A_7(WHAT, A1, A2, _0, _1, _2, _3) FEI_2A_6(WHAT, A1, A2, _0, _1, _2) WHAT(3, A1, A2, _3)
    
    #define FEI_3A_6(WHAT, A1, A2, A3, _0, _1) FEI_3A_5(WHAT, A1, A2, A3, _0) WHAT(1, A1, A2, A3, _1)
    #define FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) FEI_3A_6(WHAT, A1, A2, A3, _0, _1) WHAT(2, A1, A2, A3, _2)
    #define FEI_3A_8(WHAT, A1, A2, A3, _0, _1, _2, _3) FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) WHAT(3, A1, A2, A3, _3)
    
    #define FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) FEI_4A_6(WHAT, A1, A2, A3, A4, _0) WHAT(1, A1, A2, A3, A4, _1)
    #define FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) WHAT(2, A1, A2, A3, A4, _2)
    #define FEI_4A_9(WHAT, A1, A2, A3, A4, _0, _1, _2, _3) FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) WHAT(3, A1, A2, A3, A4, _3)
    

    这些是用于生成FOR_EACH(又名FE)宏的(规则结构的部分)宏重载组,这些宏可以发送WHAT宏可选地带有任意数量的常量参数(@987654326 @, A2...) 除了列表中的任意数量的参数,以及正确排序的索引(不使用 SELECT 之类的东西进行重载的幼稚实现会产生反向索引)。

    作为示例,剩余部分(第二个块的非常规“基本情况”部分)如下所示:

    #define FE_INDEXED_1ARG(...) VA_SELECT(FEI_1A, __VA_ARGS__)
    #define FEI_1A_3(WHAT, A1, _0) WHAT(0, A1, _0)
    

    它的实用性可能会受到质疑(我构建它是因为我看到了它的用途......),这也不能直接回答 OP 的问题(事实上,它有点相反 - foreach 构造对所有可变参数都做了相同的事情...),但我只是认为该技术非常有趣(以及在某些方面完全令人恐惧)并且具有相当大的表达能力使用预处理器,可以以这种方式生成非常有效的机器代码。我认为这也是我个人认为 C 预处理器仍有改进空间的一个令人痛心的例子。

    我的意思是,C 预处理器绝对令人讨厌,我们可能应该废弃它并从头开始 :)

    【讨论】:

      猜你喜欢
      • 2012-07-30
      • 1970-01-01
      • 1970-01-01
      • 2023-04-11
      • 1970-01-01
      • 2021-06-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多