C++ 宏的可选参数
Posted
技术标签:
【中文标题】C++ 宏的可选参数【英文标题】:Optional Parameters with C++ Macros 【发布时间】:2011-03-04 01:52:22 【问题描述】:有没有办法通过 C++ 宏获取可选参数?某种重载也不错。
【问题讨论】:
C 相同:***.com/questions/11761703/… 应该相同,因为预处理器基本相同:***.com/questions/5085533/… 也许函数重载、默认参数、可变参数模板或命名参数惯用语是您正在寻找的内容 请将您选择的答案更新为具有实际解决方案的高票数,而不是低票数No you can't
【参考方案1】:
gcc
/g++
支持varargs macros,但我认为这不是标准的,所以使用它需要您自担风险。
【讨论】:
它们是 C99 中的标准,并且它们也被添加到 C++0x 中。【参考方案2】:C++ 宏并没有从 C 中改变。因为 C 没有函数的重载和默认参数,所以它当然没有宏。所以回答你的问题:不,宏不存在这些功能。您唯一的选择是定义多个具有不同名称的宏(或根本不使用宏)。
附带说明:在 C++ 中,尽可能远离宏通常被认为是一种好的做法。如果你需要这样的功能,你很有可能过度使用宏。
【讨论】:
请注意,不能“重载”宏的原因是它们没有任何固有类型。宏只是简单地展开。 虽然我尽可能少地使用宏,但我发现通过跟踪输出进行调试会变得相当容易,例如__FILE__
和 __LINE__
等等......
不是一个好的答案。这是一个很好的答案:***.com/q/27049491/893406
条件编译和调试/记录是宏真正方便和合法的领域。每个认真的程序员都知道这一点。好的做法是不要使用宏来定义常量,而是使用一些疯狂的 C 级编码来创建容器模板。我希望 C++ 也能为宏添加更多功能。它们与模板正交。最好的当然是允许我将生成器添加到特定领域语言(方面)的编译器的小代码。
我也认为这不是一个好的答案,因为宏完全不同于任何 C++ 语言选项,因为它将在编译器之前处理。所以你可以做其他事情,没有编译器或链接器必须优化代码,因为它可能不需要优化。【参考方案3】:
根据您的需要,您可以使用带有宏的var args 来实现。现在,可选参数或宏重载,都没有。
【讨论】:
【参考方案4】:这并不是预处理器的真正用途。
也就是说,如果您想进入具有一定可读性且极具挑战性的宏编程领域,您应该看看Boost preprocessor library。毕竟,如果没有三个完全图灵兼容的编程级别(预处理器、模板元编程和基础级 C++),就不会是 C++!
【讨论】:
【参考方案5】:#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)
您在调用时就知道要传入多少个参数,因此实际上不需要重载。
【讨论】:
我实际上是在询问该功能的存在。【参考方案6】:这是一种方法。它使用参数列表两次,首先形成辅助宏的名称,然后将参数传递给辅助宏。它使用标准技巧来计算宏的参数数量。
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;
这使宏的调用者更容易,但编写者却不是。
【讨论】:
这很酷,但我认为如果我只执行 PRINT_STRING 就行不通。在那种情况下,不会有默认的打印输出(这实际上是我想要使用的情况)。仍然 +1 真的很酷。 在 gcc 中对我有用(而且非常聪明!):-) 但在 Visual Studio 中对我不起作用:-( @TimGradwell - 这是由于 MSVC 编译器中的一个错误,他们已经承认但近十年没有修复。但是,解决方法are available. 聪明,但不适用于可选的可变参数宏参数,因为您在“GET_4th_ARG”中进行了“推出”操作。 是否需要PRINT_STRING_MACRO_CHOOSER
?我可以直接用它的内部替换并用(__VA_ARGS__)
调用这整个东西吗?【参考方案7】:
#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)
的零参数
可变参数宏不存在零参数,因为空标记是有效的占位符。【参考方案8】:
非常感谢 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:删除#define for XXX_0 会导致编译错误[即:如果不允许无参数选项]。
PPS:如果无效情况(例如:5)能够为程序员提供更清晰的编译错误,那就太好了!
PPPS:我不是专家,所以我很高兴听到 cmets(好、坏或其他)!
【讨论】:
如果您使用#(井号)将应该是宏名称的选定参数转换为字符串,并将其前 n 个字符与预期的前缀进行比较,并且如果没有匹配,打印了一个信息错误。 哇,我不知道这是否有效,但至少很有创意! 为什么第一个参数总是空的?为什么我们不能省略它:XXX_X(,##__VA_ARGS__,` ...
XXX_X(, XXX_4(), XXX_3(), XXX_2(), XXX_1(), XXX_0());`
空的第一个参数(逗号)很重要。 ##__VA_ARGS__ 如果它前面有一个逗号 - 如果 ##__VA_ARGS__ 扩展为空,它会删除逗号。您可以在“成为...”示例中看到它,因为第一行(无参数)只有 6 个参数,但其余的有 7 个。这个技巧确保无参数情况有效
@Eric - 这是由于微软编译器中的一个错误,但您可以see this question 寻求解决方法。【参考方案9】:
以上示例(来自 Derek Ledbetter、David Sorkovsky 和 Joe D)都没有使用 Microsoft VCC 10 对我使用宏来计算参数。__VA_ARGS__
参数始终被视为单个参数(对其进行标记化是否使用##
),因此这些示例所依赖的论点转移不起作用。
所以,正如上面许多其他人所说,简短的回答:不,您不能重载宏或在它们上使用可选参数。
【讨论】:
您可以,但仅限于 C99 或 C++11(由于具有 __VA_ARGS__)。 VC2010 是 C89/C++03(有一些 C++11 开始出现,但还没有)。【参考方案10】:对于任何痛苦地寻找一些适用于 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
是的,在某些情况下可能会发生。你需要知道 bool(#__VA_ARGS__) 吗?与其他宏不同,因为它是在运行时进行评估的。根据您的情况,您可以省略这部分代码。
我实际上最终得到了pastebin.com/H3T75dcn,它完美地工作(0 个参数)。
感谢您的链接,是的,您也可以使用 sizeof 来完成,但对我来说,这在某些情况下不起作用,但原理是相同的(布尔评估)。
你能举一些失败的例子吗?【参考方案11】:
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;
【讨论】:
【参考方案12】:非常感谢 Derek Ledbetter、David Sorkovsky、Syphorlate 的回答,以及通过以下方法检测空宏参数的巧妙方法Jens Gustedt 在
https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
最后我想出了一个包含所有技巧的东西,所以解决方案
-
仅使用 标准 C99 宏来实现函数重载,不涉及 GCC/CLANG/MSVC 扩展(即 GCC/CLANG 的特定表达式
, ##__VA_ARGS__
的逗号吞咽,以及 @ 的隐式吞咽987654325@ MSVC)。因此,如果您愿意,请随时将缺少的 --std=c99
传递给您的编译器 =)
适用于零参数,以及无限数量的参数,如果您进一步扩展它以满足您的需要
工作合理跨平台,至少经过测试
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_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()
巧妙地,我们可以将这个生成的宏调用定义为
#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 20
和 20 ()
分别算作底部调用的 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 的一种解决方法,它在将 -pedantic
传递给编译器时抑制(误报)错误,指出 ISO C99 requires rest arguments to be used
。 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 或 4 个功能? @Phylliida ideone.com/jD0Hm5 - 支持零到五个参数。【参考方案13】:您可以使用boost
库中的BOOST_PP_OVERLOAD
。
来自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
【讨论】:
【参考方案14】:作为可怕的宏观怪物的忠实粉丝,我想扩展 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;
【讨论】:
以上是关于C++ 宏的可选参数的主要内容,如果未能解决你的问题,请参考以下文章