45

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

4

5 回答 5

63

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

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

警告:该系统无法区分零参数和一个参数,因为没有参数和单个空参数之间没有区别他们俩的样子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()
于 2013-05-22T03:47:48.207 回答
5

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

简而言之,当__VA_ARGS__为空时,EXPAND __VA_ARGS__ ()内部VA_SIZE宏变为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, 5EXPAND __VA_ARGS__ ()变为EXPAND int, 5 ()。因此,如 Potatoswatter 的回答中所述, become ,它变为并返回VA_SIZE...2 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)

我从Jason Dang 的回答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()
于 2017-07-11T19:45:56.387 回答
4

我会将其作为对 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)宏,除了在一个列表,以及一个正确排序的索引(一个不使用类似重载的简单实现会产生反向索引)。WHATA1A2SELECT

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

#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 预处理器是绝对可憎的,我们可能应该废弃它并从头开始:)

于 2014-01-04T03:33:54.387 回答
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); 
}
于 2016-12-16T04:41:32.243 回答
1

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

图书馆

#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);
于 2018-12-19T11:27:17.747 回答