带有C ++宏的可选参数

时间:2010-06-15 16:02:46

标签: c++ macros

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

14 个答案:

答案 0 :(得分:133)

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

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 :(得分:69)

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

了解它正在做什么,并在__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:我不是专家,所以我很高兴听到评论(好的,坏的或其他的)!

答案 2 :(得分:30)

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

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

答案 3 :(得分:22)

非常尊重 Derek Ledbetter David Sorkovsky Syphorlate 以获得他们的答案,以及通过巧妙的方法来检测空的宏参数 Jens Gustedt

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

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

  1. 仅使用标准C99 宏来实现功能重载,不涉及GCC / CLANG / MSVC扩展(即,通过GCC / CLANG的特定表达式, ##__VA_ARGS__吞咽逗号,以及隐式吞咽通过##__VA_ARGS__获取MSVC)。如果您希望=)
  2. ,请随意将丢失的--std=c99传递给编译器
  3. 适用于零参数,以及无限数量的参数,如果您进一步扩展以满足您的需求
  4. 合理跨平台,至少经过测试

    • 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)
  5. 对于懒惰,只需跳到本文的最后一篇即可复制源代码。以下是详细的解释,希望能帮助并激励所有寻找像我这样的一般__VA_ARGS__解决方案的人。 =)

    这是怎么回事。首先定义用户可见的重载“函数”,我将其命名为create,并将相关的实际函数定义realCreate和具有不同参数数量的宏定义CREATE_2,{{1 },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__

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

    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); 被计为中间调用的1个参数。 NO_ARG_EXPANDER 10 ()NO_ARG_EXPANDER 20分别计为底部调用的2个参数。

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

    20 ()

    ##define CHOOSE_FROM_ARG_COUNT(...) \ FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, )) #define FUNC_RECOMPOSER(argsWithParentheses) \ FUNC_CHOOSER argsWithParentheses 之后的,是GCC / CLANG的变通方法,可以在向CREATE_1传递ISO C99 requires rest arguments to be used时将-pedantic说明为{误1}错误。 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

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

答案 4 :(得分: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

答案 5 :(得分:6)

gcc / g++支持varargs macros,但我不认为这是标准的,因此请自行承担风险。

答案 6 :(得分: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 */
}

免责声明:大部分无害。

答案 7 :(得分:3)

这不是预处理器的设计目的。

也就是说,如果你想进入具有一定可读性的严重挑战宏编程领域,你应该看一下Boost preprocessor library。毕竟,如果没有三个完全图灵兼容的编程级别(预处理器,模板元编程和基本级C ++),它就不会是C ++!

答案 8 :(得分: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)

你知道你要传递多少个args,所以真的不需要重载。

答案 9 :(得分:2)

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

答案 10 :(得分:1)

您可以使用BOOST_PP_OVERLOAD库中的boost

来自official boost doc的示例:

#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

答案 11 :(得分:0)

根据您的需要,您可以使用宏var args进行操作。现在,可选参数或宏重载,没有这样的东西。

答案 12 :(得分:0)

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

所以我制作了具有以下功能的版本:

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

答案 13 :(得分:-1)

上述示例(来自Derek Ledbetter,David Sorkovsky和Joe D)都没有使用Microsoft VCC 10计算宏的参数。__VA_ARGS__参数始终被视为单个参数(令牌 - 使用##或不使用{{1}}来使其变形,因此这些示例所依赖的参数移位不起作用。

所以,简短的回答,正如上面许多其他人所说:不,你不能重载宏或使用可选参数。