C ++宏的可选参数


105

有什么方法可以通过C ++宏获取可选参数?某种重载也是很好的。



您可能正在寻找函数重载,默认参数,可变参数模板或命名参数惯用语
平滑软件,2015年

请使用实际的解决方案更新您选择的答案,以高评价的答案为准,而不是低评价的说法No you can't
Albert Renshaw

Answers:


156

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

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;
}

这使宏的调用者(而不是编写者)更容易。


1
这很酷,但是我认为只要执行PRINT_STRING,它就不会起作用。在那种情况下,将没有默认的打印输出(而这正是我要利用的情况)。还是+1真酷。
Cenoc 2010年

2
在gcc中为我工作(非常聪明!):-),但在Visual Studio中不为我工作:-(
Tim Gradwell 2012年

3
@TimGradwell-这是由于MSVC编译器中的一个错误,他们已经认识到该错误,但近十年来仍未修复。但是,可以使用解决方法。
BeeOnRope

聪明,但不适用于可选的可变参数宏参数,因为您在“ GET_4th_ARG”中执行了“推出”操作。
searchengine27

是,PRINT_STRING_MACRO_CHOOSER即使需要?我可以直接替换其内部物体并将其称为(__VA_ARGS__)吗?
Herrgott

85

非常感谢德里克·莱德贝特(Derek Ledbetter)的回答-并为重提旧问题深表歉意。

但把它做什么,到preceed的能力在其他地方拿起的理解__VA_ARGS__##让我拿出一个变化...

// The multiple macros that you would need anyway [as per: Crazy Eddie]
#define XXX_0()                     <code for no arguments> 
#define XXX_1(A)                    <code for one argument> 
#define XXX_2(A,B)                  <code for two arguments> 
#define XXX_3(A,B,C)                <code for three arguments> 
#define XXX_4(A,B,C,D)              <code for four arguments>  

// 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:我不是专家,所以很高兴听到您的评论(好,坏或其他)!


3
如果使用#(井号)将选定的参数(应该是MACRO名称)转换为字符串,并将其前n个字符与预期的前缀进行比较,并且没有匹配项,则打印出信息丰富的信息,可能会导致明显的编译错误错误。
AturSams 2014年

1
哇,我不知道这行不通,但是至少很有创意!
限量赎罪

4
为什么第一个参数总是空的?为什么我们不能忽略它:XXX_X(,##__VA_ARGS__,` ... XXX_X(,XXX_4(),XXX_3(),XXX_2(),XXX_1(),XXX_0());`
rahman

2
空的第一个参数(逗号)很重要。## __ VA_ARGS __(如果前面带有逗号)–如果## __ VA_ARGS__扩展为零,则删除逗号。您可以在“ Becomes ...”示例中看到它,因为第一行(无参数)只有6个参数,其余的只有7个参数。此技巧可确保无参数的情况有效
David Sorkovsky

@Eric-这是由于Microsoft编译器中的错误所致,但是您可以看到此问题的解决方法。
BeeOnRope

31

C ++宏与C相比没有变化。由于C没有函数的重载和默认参数,因此它肯定没有宏。因此,请回答您的问题:不,宏不存在这些功能。您唯一的选择是定义多个具有不同名称的宏(或根本不使用宏)。

附带说明:在C ++中,通常认为最好是尽可能远离宏。如果您需要这样的功能,则很有可能会过度使用宏。


4
请注意,不可能“重载”宏的原因是因为它们没有任何固有类型。宏只是被扩展。
mk12 2012年

2
虽然我用宏尽可能少的,我发现,通过跟踪输出调试变得相当容易一点的东西像__FILE____LINE__与这样的...
基督教塞韦林

不是一个好的答案。这是一个很好的答案:stackoverflow.com/q/27049491/893406
v.oddou

条件编译和调试/日志记录是宏真正方便且合法的领域。每个认真的程序员都知道这一点。好的做法是避免使用宏来定义常量,并进行一些疯狂的C级编码工作来创建容器模板。我希望C ++也可以为宏添加更多功能。它们与模板正交。最好的当然是小代码,这些代码允许我将生成器添加到针对特定领域语言的方面的编译器(方面)。
Lothar

1
我也认为这不是一个好答案,因为宏是除任何C ++语言选项之外的完全替代的东西,因为它将在编译器之前进行处理。因此,您可以做其他事情,并且编译器或链接器都不必优化代码,因为可能是不对其进行优化。
alabamajack

26

非常感谢Derek LedbetterDavid SorkovskySyphorlate的回答,以及Jens Gustedt

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

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

  1. 仅使用标准C99宏来实现函数重载,不涉及GCC / CLANG / MSVC扩展(即,, ##__VA_ARGS__对于GCC / CLANG,用特定表达式表示逗号吞咽,##__VA_ARGS__对于MSVC ,用隐式表示吞咽)。所以随时通过失踪者--std=c99如果您愿意,可以给编译器=)
  2. 如果您进一步扩展它以满足您的需要,则适用于零参数以及无限数量的参数
  3. 合理地跨平台工作,至少经过测试

    • GNU / Linux + GCC(CentOS 7.0 x86_64上的GCC 4.9.2)
    • 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_2CREATE_1CREATE_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()

巧妙地,我们可以将此宏调用定义为

#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 <stdio.h>

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
如何将其扩展到3或4个功能?
Phylliida

@Phylliida ideone.com/jD0Hm5-支持零到五个参数。
xx

9

对于任何痛苦地搜索与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

我得到unresolved external symbol _bool referenced in function _main
Avidan Borisov

是的,在某些情况下可能会发生。您需要知道bool(#__ VA_ARGS__)吗?与其他宏不同,因为它是在运行时进行评估的。但是,根据您的情况,您可以省略部分代码。
2013年

2
实际上,我最终得到了pastebin.com/H3T75dcn,它可以完美运行(也是0个参数)。
阿维丹·鲍里索夫

感谢您的链接,是的,您也可以使用sizeof来完成,但是对我来说,在某些情况下不起作用,但原理是相同的(布尔值评估)。
2013年

您能举一些失败的例子吗?
Avidan Borisov


5
#include <stdio.h>

#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 */
}

免责声明:多数情况下无害。


您的代码中有错误。请你:%s/MY_MACRO_/THINK_/g:)
若昂·波特拉

同时,它并没有使用g个零个参数++工作i686-apple-darwin10-g++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)
若昂·波特拉

1
variadiac宏不存在零参数,因为空令牌是有效的占位符。
Paul Fultz II

3

这并不是预处理器的真正目的。

就是说,如果您想进入具有极大可读性的严峻挑战的宏编程领域,则应该看看Boost预处理程序库。毕竟,如果没有三个与Turing完全兼容的编程级别(预处理器,模板元编程和基础级别的C ++),那将不是C ++!


3
#define MY_MACRO_3(X,Y,Z) ...
#define MY_MACRO_2(X,Y) MY_MACRO(X,Y,5)
#define MY_MACRO_1(X) MY_MACRO(X,42,5)

您知道在调用时要传递多少个arg,因此实际上不需要重载。


2
我实际上是在询问功能的存在。
Cenoc

3

Derek Ledbetter的代码的更简洁版本:

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


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


#define PRINT_STRING(...) PrintString(__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;
}

3

作为可怕的巨型怪物的忠实拥护者,我想扩展杰森·邓的答案,并使其实际可用。(不管是好是坏。)原始版本不太好用,因为每次要创建新的宏时都需要修改大字母汤,如果需要不同数量的参数,则更糟。

因此,我制作了具有以下功能的版本:

  • 0个论证案例
  • 1至16个参数,无需对凌乱的部分进行任何修改
  • 易于编写更多宏功能
  • 在gcc 10,clang 9,Visual Studio 2017中测试

目前,我最多只能输入16个参数,但是如果您需要更多参数(真的吗?您现在变得傻了...),您可以编辑FUNC_CHOOSER和CHOOSE_FROM_ARG_COUNT,然后在NO_ARG_EXPANDER中添加一些逗号。

有关实现的更多详细信息,请参见Jason Deng的出色答案,但我将代码放在这里:

#include <stdio.h>

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

// This part you put in some library header:
#define FUNC_CHOOSER(_f0, _f1, _f2, _f3, _f4, _f5, _f6, _f7, _f8, _f9, _f10, _f11, _f12, _f13, _f14, _f15, _f16, ...) _f16
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(F, ...) FUNC_RECOMPOSER((__VA_ARGS__, \
            F##_16, F##_15, F##_14, F##_13, F##_12, F##_11, F##_10, F##_9, F##_8,\
            F##_7, F##_6, F##_5, F##_4, F##_3, F##_2, F##_1, ))
#define NO_ARG_EXPANDER(FUNC) ,,,,,,,,,,,,,,,,FUNC ## _0
#define MACRO_CHOOSER(FUNC, ...) CHOOSE_FROM_ARG_COUNT(FUNC, NO_ARG_EXPANDER __VA_ARGS__ (FUNC))
#define MULTI_MACRO(FUNC, ...) MACRO_CHOOSER(FUNC, __VA_ARGS__)(__VA_ARGS__)

// When you need to make a macro with default arguments, use this:
#define create(...) MULTI_MACRO(CREATE, __VA_ARGS__)
#define CREATE_0() CREATE_1(0)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_2(x, y) \
    do { \
        /* put whatever code you want in the last macro */ \
        realCreate(x, y); \
    } while(0)


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

2

您可以BOOST_PP_OVERLOADboost库中使用。

官方boost文档的示例:

#include <boost/preprocessor/facilities/overload.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/arithmetic/add.hpp>

#define MACRO_1(number) MACRO_2(number,10)
#define MACRO_2(number1,number2) BOOST_PP_ADD(number1,number2)

#if !BOOST_PP_VARIADICS_MSVC

#define MACRO_ADD_NUMBERS(...) BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__)

#else

// or for Visual C++

#define MACRO_ADD_NUMBERS(...) \
  BOOST_PP_CAT(BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())

#endif

MACRO_ADD_NUMBERS(5) // output is 15
MACRO_ADD_NUMBERS(3,6) // output is 9

0

根据您的需要,可以使用带有宏的var args。现在,可选参数或宏重载已不存在。


-1

上面的示例(来自Derek Ledbetter,David Sorkovsky和Joe D)都没有一个使用Microsoft VCC 10对宏进行参数计数对我有用。该__VA_ARGS__参数始终被视为单个参数(使用##或不使用),因此这些例子所依赖的论据转移是行不通的。

因此,简短的答案,正如上面许多其他人所述:不,您不能重载宏或在宏上使用可选参数。


1
您可以,但只能在C99或C ++ 11中使用(由于拥有__VA_ARGS__)。VC2010是C89 / C ++ 03(有些C ++ 11开始出现,但还没有)。
puetzk 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.