[英]Optional Parameters with C++ Macros
是否有某種方法可以使用 C++ 宏獲取可選參數? 某種重載也會很好。
這是一種方法。 它兩次使用參數列表,首先形成助手宏的名稱,然后將參數傳遞給該助手宏。 它使用標准技巧來計算宏參數的數量。
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;
}
這使宏的調用者(而不是寫者)更容易。
非常感謝德里克·勒德貝特(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:我不是專家,所以很高興聽到您的評論(好,壞或其他)!
C ++宏沒有從C更改。由於C沒有重載和函數的默認參數,因此它肯定沒有宏。 因此,請回答您的問題:不,宏不存在這些功能。 您唯一的選擇是定義多個具有不同名稱的宏(或根本不使用宏)。
附帶說明:在C ++中,通常認為最好是盡可能遠離宏。 如果您需要這樣的功能,則很有可能會過度使用宏。
非常感謝 Derek Ledbetter , David Sorkovsky , Syphorlate的回答,以及Jens Gustedt在
https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
最終,我提出了包含所有技巧的東西,以便解決方案
, ##__VA_ARGS__
為GCC / CLANG和隱含吞咽由##__VA_ARGS__
為MSVC)。 因此,如果願意,可以將缺少的--std=c99
傳遞給編譯器 合理地跨平台工作 ,至少經過測試
對於懶惰者,只需跳到該帖子的最后以復制源。 下面是詳細的解釋,希望可以幫助並激發所有尋求像我這樣的通用__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__
為空,則預處理器__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,抑制(假陽性)錯誤,指出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
再次特別感謝所有啟發並帶領我完成這項工作的傑出人士! =)
對於任何痛苦地搜索與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
gcc
/ g++
支持varargs宏,但是我不認為這是標准的,因此使用它需要您自擔風險。
#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 */
}
免責聲明: 多數情況下無害。
那不是真的預處理器的目的。
就是說,如果您想以很少的可讀性進入嚴峻挑戰的宏編程領域,那么應該看看Boost預處理程序庫 。 畢竟,如果沒有三個與Turing完全兼容的編程級別(預處理器,模板元編程和基礎級別的C ++),那將不是C ++!
#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,因此實際上不需要重載。
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;
}
您可以從boost
庫中使用BOOST_PP_OVERLOAD
。
官方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
作為可怕的巨型怪物的忠實擁護者,我想擴展傑森·鄧的答案,並使其實際可用。 (不管是好是壞),因為使用原始的效果不是很好,因為每次要創建新的宏時都需要修改較大的字母湯,如果需要不同數量的參數,則更糟。
因此,我制作了具有以下功能的版本:
目前,我最多只設置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;
}
根據需要,可以使用帶有宏的var args 。 現在,可選參數或宏重載已不存在。
不是直接回答問題,而是使用與David Sorkovsky回答相同的技巧,並給出了如何構建復雜宏的清晰示例。
只需使用g++ -E test.cpp -o test && cat test
編譯它:
// #define GET_FIRST_ARG_0_ARGS(default) (default)
// #define GET_FIRST_ARG_1_ARGS(default, a) (a)
// #define GET_FIRST_ARG_2_ARGS(default, a, b) (a)
// #define GET_FIRST_ARG_3_ARGS(default, a, b, c) (a)
// #define GET_FIRST_ARG_4_ARGS(default, a, b, c, d) (a)
#define GET_FIRST_ARG_MACROS(default, a, b, c, d, macro, ...) macro
#define GET_FIRST_ARG(default, ...) GET_FIRST_ARG_MACROS( \
,##__VA_ARGS__, \
GET_FIRST_ARG_4_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_3_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_2_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_1_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_0_ARGS(default, ##__VA_ARGS__), \
)
"0,"; GET_FIRST_ARG(0);
"0,1"; GET_FIRST_ARG(0,1);
"0,1,2"; GET_FIRST_ARG(0,1,2);
"0,1,2,3"; GET_FIRST_ARG(0,1,2,3);
"0,1,2,3,4"; GET_FIRST_ARG(0,1,2,3,4);
要查看 output:
# 1 "test.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/x86_64-linux-gnu/include/stdc-predef.h" 1 3
# 1 "<command-line>" 2
# 1 "test.cpp"
# 16 "test.cpp"
"0,"; GET_FIRST_ARG_0_ARGS(0);
"0,1"; GET_FIRST_ARG_1_ARGS(0, 1);
"0,1,2"; GET_FIRST_ARG_2_ARGS(0, 1,2);
"0,1,2,3"; GET_FIRST_ARG_3_ARGS(0, 1,2,3);
"0,1,2,3,4"; GET_FIRST_ARG_4_ARGS(0, 1,2,3,4);
現在,一個完整的工作程序將是:
#include <iostream>
#define GET_FIRST_ARG_0_ARGS(default) (default)
#define GET_FIRST_ARG_1_ARGS(default, a) (a)
#define GET_FIRST_ARG_2_ARGS(default, a, b) (a)
#define GET_FIRST_ARG_3_ARGS(default, a, b, c) (a)
#define GET_FIRST_ARG_4_ARGS(default, a, b, c, d) (a)
#define GET_FIRST_ARG_MACROS(default, a, b, c, d, macro, ...) macro
#define GET_FIRST_ARG(default, ...) GET_FIRST_ARG_MACROS( \
,##__VA_ARGS__, \
GET_FIRST_ARG_4_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_3_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_2_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_1_ARGS(default, __VA_ARGS__), \
GET_FIRST_ARG_0_ARGS(default, ##__VA_ARGS__), \
)
int main(int argc, char const *argv[]) {
"0,"; GET_FIRST_ARG(0);
"0,1"; GET_FIRST_ARG(0,1);
"0,1,2"; GET_FIRST_ARG(0,1,2);
"0,1,2,3"; GET_FIRST_ARG(0,1,2,3);
"0,1,2,3,4"; GET_FIRST_ARG(0,1,2,3,4);
std::cerr << "0, == " << GET_FIRST_ARG(0) << std::endl;
std::cerr << "0,1 == " << GET_FIRST_ARG(0,1) << std::endl;
std::cerr << "0,1,2 == " << GET_FIRST_ARG(0,1,2) << std::endl;
std::cerr << "0,1,2,3 == " << GET_FIRST_ARG(0,1,2,3) << std::endl;
std::cerr << "0,1,2,3,4 == " << GET_FIRST_ARG(0,1,2,3,4) << std::endl;
return 0;
}
通過使用g++ test.cpp -o test &&./test
編譯 output 以下內容:
0, == 0
0,1 == 1
0,1,2 == 1
0,1,2,3 == 1
0,1,2,3,4 == 1
注意:當a
不是 integer 時,在宏內容周圍使用()
作為#define GET_FIRST_ARG_1_ARGS(default, a) (a)
很重要,以免在不明確的表達式中中斷。
上面的示例(來自Derek Ledbetter,David Sorkovsky和Joe D)都沒有一個使用Microsoft VCC 10對宏進行參數計數對我__VA_ARGS__
參數始終被視為單個參數(是否使用##
對其進行令牌化),因此這些示例所依賴的參數轉換不起作用。
因此,正如上面其他許多人所述,答案很簡短:不,您不能重載宏或在宏上使用可選參數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.