当前位置:  开发笔记 > 编程语言 > 正文

带有C++宏的可选参数

如何解决《带有C++宏的可选参数》经验,为你挑选了7个好方法。

有没有办法用C++宏获取可选参数?某种超载也会很好.



1> Derek Ledbet..:

这是一种方法.它使用两次参数列表,首先形成辅助宏的名称,然后将参数传递给该帮助器宏.它使用标准技巧来计算宏的参数数量.

enum
{
    plain = 0,
    bold = 1,
    italic = 2
};

void PrintString(const char* message, int size, int style)
{
}

#define PRINT_STRING_1_ARGS(message)              PrintString(message, 0, 0)
#define PRINT_STRING_2_ARGS(message, size)        PrintString(message, size, 0)
#define PRINT_STRING_3_ARGS(message, size, style) PrintString(message, size, style)

#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
#define PRINT_STRING_MACRO_CHOOSER(...) \
    GET_4TH_ARG(__VA_ARGS__, PRINT_STRING_3_ARGS, \
                PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, )

#define PRINT_STRING(...) PRINT_STRING_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

int main(int argc, char * const argv[])
{
    PRINT_STRING("Hello, World!");
    PRINT_STRING("Hello, World!", 18);
    PRINT_STRING("Hello, World!", 18, bold);

    return 0;
}

这使宏的调用者更容易,但不是编写者.


在gcc中为我工作(它非常聪明!):-)但在Visual Studio中对我不起作用:-(
@TimGradwell-这是由于MSVC编译器中的一个错误,他们已经认识到该错误,但近十年来仍未修复。但是,解决方法[可用](/sf/ask/17360801/)。

2> David Sorkov..:

非常尊重Derek Ledbetter的回答 - 并为复兴旧问题道歉.

了解它正在做什么,并在其他地方找到能够在之前的能力__VA_ARGS__,##让我想出一个变化......

// The multiple macros that you would need anyway [as per: Crazy Eddie]
#define XXX_0()                      
#define XXX_1(A)                     
#define XXX_2(A,B)                   
#define XXX_3(A,B,C)                 
#define XXX_4(A,B,C,D)                

// The interim macro that simply strips the excess and ends up with the required macro
#define XXX_X(x,A,B,C,D,FUNC, ...)  FUNC  

// The macro that the programmer uses 
#define XXX(...)                    XXX_X(,##__VA_ARGS__,\
                                          XXX_4(__VA_ARGS__),\
                                          XXX_3(__VA_ARGS__),\
                                          XXX_2(__VA_ARGS__),\
                                          XXX_1(__VA_ARGS__),\
                                          XXX_0(__VA_ARGS__)\
                                         ) 

对于像我这样偶然发现答案的非专家,但不能完全看出它是如何工作的,我将逐步完成实际处理,从以下代码开始......

XXX();
XXX(1); 
XXX(1,2); 
XXX(1,2,3); 
XXX(1,2,3,4); 
XXX(1,2,3,4,5);      // Not actually valid, but included to show the process 

成为...

XXX_X(, XXX_4(), XXX_3(),  XXX_2(),    XXX_1(),      XXX_0()         );
XXX_X(, 1,       XXX_4(1), XXX_3(1),   XXX_2(1),     XXX_1(1),       XXX_0(1)          );
XXX_X(, 1,       2,        XXX_4(1,2), XXX_3(1,2),   XXX_2(1,2),     XXX_1(1,2),       XXX_0(1,2)        );
XXX_X(, 1,       2,        3,          XXX_4(1,2,3), XXX_3(1,2,3),   XXX_2(1,2,3),     XXX_1(1,2,3),     XXX_0(1,2,3)      );
XXX_X(, 1,       2,        3,          4,            XXX_4(1,2,3,4), XXX_3(1,2,3,4),   XXX_2(1,2,3,4),   XXX_1(1,2,3,4),   XXX_0(1,2,3,4)    );
XXX_X(, 1,       2,        3,          4,            5,              XXX_4(1,2,3,4,5), XXX_3(1,2,3,4,5), XXX_2(1,2,3,4,5), XXX_1(1,2,3,4,5), XXX_0(1,2,3,4,5) );

这成为第六个论点......

XXX_0(); 
XXX_1(1); 
XXX_2(1,2); 
XXX_3(1,2,3); 
XXX_4(1,2,3,4); 
5; 

PS:删除XXX_0的#define以获得编译错误[即:如果不允许无参数选项].

PPS:很高兴有无效的情况(例如:5)会给程序员带来更清晰的编译错误!

PPPS:我不是专家,所以我很高兴听到评论(好的,坏的或其他的)!


+1,感谢有关它如何工作的解释.
如果您使用#(英镑符号)将所选的应该是MACRO名称的参数转换为字符串并将其前n个字符与预期前缀进行比较,并且如果没有匹配,则可以获得明确的编译错误,错误.
为什么第一个参数总是空的?为什么我们不能省略它:`XXX_X(,## __ VA_ARGS __,\`...`XXX_X(,XXX_4(),XXX_3(),XXX_2(),XXX_1(),XXX_0());;
空的第一个参数(逗号)很重要。## __ VA_ARGS __(如果前面带有逗号)–如果## __ VA_ARGS__扩展为零,则删除逗号。您可以在“ Becomes ...”示例中看到它,因为第一行(无参数)只有6个参数,其余的只有7个参数。此技巧可确保无参数情况有效

3> sepp2k..:

C++宏没有从C改变.由于C没有函数的重载和默认参数,它当然没有它们用于宏.所以回答你的问题:不,这些功能不存在于宏.您唯一的选择是定义多个具有不同名称的宏(或根本不使用宏).

作为旁注:在C++中,通常认为尽可能远离宏是一种好习惯.如果你需要这样的功能,你很可能会过度使用宏.


请注意,不可能"重载"宏的原因是因为它们没有任何固有类型.宏只是扩展了.
它实际上是一个快速代码生成的东西.
虽然我尽可能少地使用宏,但我发现通过跟踪输出进行调试可以通过`__FILE__`和`__LINE__`这样的事情变得更加容易......

4> Jason Deng..:

随着最大的关于德里克莱德贝特,大卫Sorkovsky,Syphorlate对于自己的答案,用巧妙的方法,通过检测到空宏参数一起延Gustedt

https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

最后,我提出了一些包含所有技巧的东西,以便解决方案

    仅使用标准C99宏来实现功能过载,不涉及GCC/CLANG/MSVC扩展(即,通过, ##__VA_ARGS__GCC/CLANG 的特定表达式吞咽逗号,以及通过##__VA_ARGS__MSVC 隐式吞咽).--std=c99如果您希望=),请随意将遗漏传递给您的编译器

    如果您进一步扩展以满足您的需求,则可以使用零参数以及无限数量的参数

    工作合理地跨平台,至少测试过

    GNU/Linux + GCC(CentOS 7.0上的GCC 4.9.2 x86_64)

    GNU/Linux + CLANG/LLVM,(CentOS 7.0 x86_64上的CLANG/LLVM 3.5.0)

    OS X + Xcode,(OS X Yosemite 10.10.1上的XCode 6.1.1)

    Windows + Visual Studio,(Windows 7 SP1 64位上的Visual Studio 2013 Update 4)

对于懒惰,只需跳到这篇文章的最后一篇来复制源代码.下面是详细的解释,希望能帮助和激励所有寻找__VA_ARGS__像我这样的一般解决方案的人.=)

这是怎么回事.首先定义用户可见的重载"功能",我把它命名create,以及相关的实际功能的定义realCreate,并用不同数量的参数的宏定义CREATE_2,CREATE_1,CREATE_0,如下图所示:

#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

void realCreate(int x, int y)
{
  printf("(%d, %d)\n", x, y);
}

#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)

MACRO_CHOOSER(__VA_ARGS__)部分最终解析为宏定义名称,第二(__VA_ARGS__)部分包含其参数列表.所以用户要求create(10)解析CREATE_1(10),CREATE_1部分来自MACRO_CHOOSER(__VA_ARGS__),(10)部分来自第二部分(__VA_ARGS__).

MACRO_CHOOSER使用了,如果招__VA_ARGS__是空的,下面的表达式连接成由预处理器有效的宏调用:

NO_ARG_EXPANDER __VA_ARGS__ ()  // simply shrinks to NO_ARG_EXPANDER()

Ingeniusly,我们可以将这个结果宏调用定义为

#define NO_ARG_EXPANDER() ,,CREATE_0

请注意两个逗号,它们很快就会解释.下一个有用的宏是

#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())

所以电话

create();
create(10);
create(20, 20);

实际上扩展到了

CHOOSE_FROM_ARG_COUNT(,,CREATE_0)();
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 10 ())(10);
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 20, 20 ())(20, 20);

正如宏名称所示,我们稍后将计算参数的数量.这里有另一个技巧:预处理器只进行简单的文本替换.它仅根据它在括号内看到的逗号数来推断宏调用的参数个数.用逗号分隔的实际"参数"不必是有效的语法.它们可以是任何文本.也就是说,在上面的例子中,NO_ARG_EXPANDER 10 ()被计为中间调用的1个参数.NO_ARG_EXPANDER 2020 ()分别计入底部调用的2个参数.

如果我们使用以下帮助程序宏来进一步扩展它们

##define CHOOSE_FROM_ARG_COUNT(...) \
  FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define FUNC_RECOMPOSER(argsWithParentheses) \
  FUNC_CHOOSER argsWithParentheses

尾随,CREATE_1是一个变通的GCC/CLANG,抑制(假阳性)错误说,ISO C99 requires rest arguments to be used路过的时候-pedantic给你的编译器.这FUNC_RECOMPOSER是MSVC的解决方法,或者它无法正确计算宏调用括号内的参数数量(即逗号).结果进一步解决

FUNC_CHOOSER (,,CREATE_0, CREATE_2, CREATE_1, )();
FUNC_CHOOSER (NO_ARG_EXPANDER 10 (), CREATE_2, CREATE_1, )(10);
FUNC_CHOOSER (NO_ARG_EXPANDER 20, 20 (), CREATE_2, CREATE_1, )(20, 20);

正如您可能已经看过的那只老鹰眼,我们需要的最后一步是使用标准参数计数技巧来最终选择想要的宏版本名称:

#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3

将结果解析为

CREATE_0();
CREATE_1(10);
CREATE_2(20, 20);

并且肯定会给我们所需的实际函数调用:

realCreate(0, 0);
realCreate(10, 10);
realCreate(20, 20);

综合起来,通过一些重新排列的语句以提高可读性,2参数示例完整来源如下:

#include 

void realCreate(int x, int y)
{
  printf("(%d, %d)\n", x, y);
}

#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)

#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(...) FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define NO_ARG_EXPANDER() ,,CREATE_0
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())
#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

int main()
{
  create();
  create(10);
  create(20, 20);
  //create(30, 30, 30);  // Compilation error
  return 0;
}

虽然复杂,丑陋,给API开发人员带来负担,但是为我们疯狂的人们提供了重载和设置C/C++函数可选参数的解决方案.使用即将出现的重载API变得非常愉快和愉快.=)

如果这种方法有任何进一步的简化,请告诉我

https://github.com/jason-deng/C99FunctionOverload

再次特别感谢所有鼓舞并带领我完成这项工作的优秀人才!=)


如何将其扩展为3或4个函数?

5> 小智..:

对于任何痛苦地搜索适用于Visual C++的VA_NARGS解决方案的人.以下宏在visual c ++ express 2010中完美地为我工作(也是零参数!):

#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,N,...) N
#define VA_NUM_ARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple
#define VA_NARGS(...)  bool(#__VA_ARGS__) ? (VA_NUM_ARGS_IMPL_((__VA_ARGS__, 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

如果你想要一个带有可选参数的宏,你可以这样做:

//macro selection(vc++)
#define SELMACRO_IMPL(_1,_2,_3, N,...) N
#define SELMACRO_IMPL_(tuple) SELMACRO_IMPL tuple
#define mymacro1(var1) var1
#define mymacro2(var1,var2) var2*var1
#define mymacro3(var1,var2,var3) var1*var2*var3
#define mymacro(...) SELMACRO_IMPL_((__VA_ARGS__, mymacro3(__VA_ARGS__), mymacro2(__VA_ARGS__), mymacro1(__VA_ARGS__))) 

这对我和vc都很有用.但它不适用于零参数.

int x=99;
x=mymacro(2);//2
x=mymacro(2,2);//4
x=mymacro(2,2,2);//8


我实际上最终使用了http://pastebin.com/H3T75dcn,它完美地工作(0个参数).

6> Paul R..:

gcc/ g++支持varargs宏,但我不认为这是标准的,所以使用它需要您自担风险.


它们在C99中是标准的,它们也被添加到C++ 0x中.

7> Joe D..:
#include 

#define PP_NARG(...) \
    PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
    PP_ARG_N(__VA_ARGS__)
#define PP_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 PP_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

#define PP_CONCAT(a,b) PP_CONCAT_(a,b)
#define PP_CONCAT_(a,b) a ## b

#define THINK(...) PP_CONCAT(THINK_, PP_NARG(__VA_ARGS__))(__VA_ARGS__)
#define THINK_0() THINK_1("sector zz9 plural z alpha")
#define THINK_1(location) THINK_2(location, 42)
#define THINK_2(location,answer) THINK_3(location, answer, "deep thought")
#define THINK_3(location,answer,computer) \
  printf ("The answer is %d. This was calculated by %s, and a computer to figure out what this"
          " actually means will be build in %s\n", (answer), (computer), (location))

int
main (int argc, char *argv[])
{
  THINK (); /* On compilers other than GCC you have to call with least one non-default argument */
}

免责声明:大部分无害.

推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有