• C99可变参数宏(Variadic Macros) 打印: __VA_ARGS__ --- print(...)


          Tips: 这篇文章比较长,像太阳能手电筒。总结结果在最后一段代码。想省时间的小友请下拉到最后copy! 

          众所周知,C/C++ 宏(macro) 不能递归,不能重载。

           C99 标准的可变参数宏(__VA_ARGS__)无法用循环把它一个个参数分拆开来。

           如果你要打印(或处理)一连串n个不同类型(type)的变量       。比如

    void print(1, 2.0f, 'a', 3e-2, "UFO", 18u);

           你可能会用

    cout << 1 << 2.0f << 'a' << 3e-2 << "UFO" << 18u << endl;

           但是你可能会觉得 << 很烦,而且只能用于输出。如果你要加个函数来处理每个参数,如typeid("UFO").name(),那输出一行就会非常长。

           你可能会想到用 va_arg,很遗憾的告诉你,必须要求所有参数类型一样。否则,会对短字节类型做向上提升。

    1. #define printList(Func, type, cnt, ...) \
    2. { \
    3. va_list ls; \
    4. va_start(ls, cnt); \
    5. for (int i = 0; i < cnt; i++) { \
    6. cout << Func(va_arg(ls, type)) << " "; \
    7. } \
    8. cout << endl; \
    9. }

           C++11标准引入了可变参数模板,你可以很方便的打印(或处理)一连串n个不同类型(type)的变量。

    1. template<class T>
    2. void printType(T &t) {cout << typeid(t).name() << endl;}
    3. template<class T, class... Args>
    4. void printType(T &t, Args&... rest) {
    5. cout << typeid(t).name() << ', ';
    6. printType(rest...);
    7. }
    8. // printType('a', 1, 2.0, "hello");
    9. // 输出:
    10. // char, int, double, const char*

           在C++11标准前,如果你要打印一串n个不同类型(type)的变量,你可能需要重载n个不同的打印函数 void print(T a){}。T = T1, T2, ... Tn. 或者采用嵌套的宏一层一层叠加输出。

    1. #define print1(_1) cout << _1 << endl;
    2. #define print2(_1, _2) cout << _1 << ", "; print1(_2)
    3. #define print3(_1, ...) cout << _1 << ", "; print2(__VA_ARGS__)
    4. //...
    5. #define printN(_1, ...) cout << _1 << ", "; printN_1(__VA_ARGS__)

           有N个变量,就有N个宏,如果N的数量很大,可能你要费好大劲来写这N个宏。

           那么有没有办法利用 宏, 模板特化, 函数重载等手段,模拟构造一个类似C++11的可变参数打印宏呢?

           直到我看到下面这篇文章,受到了启发。贴出来部分代码,

           宏循环 - 标准替代GCC的##__ VA_ARGS__技巧?

    1. #define _SELECT(PREFIX, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    2. #define _print_1(fmt) printf(fmt "\n")
    3. #define _print_N(fmt, ...) printf(fmt "\n", ## __VA_ARGS__)
    4. #define println(...) _SELECT(_print, ## __VA_ARGS__, N, N, N, N, 1)(__VA_ARGS__)
    5. // 若要自定义处理宏。如自定义2个参数时的处理宏,
    6. // 则要添加_print_2()和修改 println 倒数第 1 个N为 2 ,参考my_println()。
    7. #define _print_2(fmt, ...) printf(fmt "\n", ## __VA_ARGS__)
    8. #define my_println(...) _SELECT(_print, ## __VA_ARGS__, N, N, N, 2, 1)(__VA_ARGS__)
    9. int main1(int argc, char *argv[]) {
    10. println("here is a log message");
    11. println("here is a log message with a param: %d", 42);
    12. my_println("here is a log message with a param: %d", 42);
    13. my_println("here is a log message with a param: %d %d %s %d", 42, 33, "abc", 13);
    14. return 0;
    15. }
    16. // _SELECT(PREFIX, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    17. // _SELECT(_print,'a', N, N, N, 2, 1 ) ('a')

           这里_SELECT()宏就像一台秤上的尺子(Ruler),SUFFIX就像尺子上的游标。开始__VA_ARGS__为1个参数时,SUFFIX对应 到 1 的位置。只要加一个参数,SUFFIX就前移一个位置,对应到相应的数字上。借助这条尺子,可以根据不同参数个数,构造不同的打印宏 _print_1, _print_2, ...。没有构造,则默认调用_print_N()。

           那么,我们是否可以利用这条尺子,来构造一套宏,组成不同参数个数的调用宏呢?当然可以。但我们不能像前面那样使用"层叠宏”那么stupid的方式。想像一下,能否使用幂增的方式,利用 m 个宏,来构造适配 2^m 个不同参数(个数可变)的调用方式呢?下面给出一个例子:

    1. #define _SELECT_10(PREFIX, _N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    2. template<typename T>
    3. void _output_0(T _1){
    4. cout << _1 << ", ";
    5. }
    6. void _output_0(){}
    7. #define _output_() cout << "None Args!" << endl;
    8. #define _output_N(...) cout << "Args' count overflow!\n";
    9. #define _output_1(_1) cout << _1;
    10. #define _output_2(_1, ...) _output_1(_1) cout << ", "; _output_0(__VA_ARGS__);
    11. #define _output_4(_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__)
    12. #define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) _output_4(__VA_ARGS__)
    13. #define output(...) _SELECT_10(_output, ##__VA_ARGS__, N, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1)(__VA_ARGS__)
    14. #define outputLn(...) output(__VA_ARGS__) cout << endl;
    15. // output(1) => SUFFIX=1 => _output_1()
    16. // output(1, 2) => SUFFIX=2 => _ouput_2(1, 2) => _output_1(1) cout<<","; _output0(2)
    17. // output(1, 2, 3) => SUFFIX=4 => _output_4(1, 2, 3) => _output_2(1, 2) _output_2(3)
    18. // => _output_2(1, 2) _output_1(1) cout < ","; _output_0();
    19. // => _output_1(1) cout < ","; _output_0(2); _output_1(1) cout < ","; _output_0();

           从下面尺子和游标对齐可以看出,

    1. // 0 个参数时:
    2. _SELECT_10(PREFIX,
    3. _N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    4. _SELECT_10(_output,
    5. N, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1)() => _output_
    6. // 1 个参数时:
    7. _SELECT_10(PREFIX,
    8. _N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    9. _SELECT_10(_output,
    10. '1', N, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1 ) ('1') => _output_1('1')
    11. // 2 个参数时:
    12. _SELECT_10(PREFIX,
    13. _N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    14. _SELECT_10(_output,
    15. '1','2', N, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1 ) ('1','2')
    16. => _output_2 ('1','2')
    17. // 3 个参数时:
    18. _SELECT_10(PREFIX,
    19. _N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    20. _SELECT_10(_output,
    21. '1','2','3', N, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1 ) ('1','2','3')
    22. => _output_4 ('1','2','3')

    当有1个参数时,SUFFIX对应 1,应用宏_output_1();

    当有2个参数时,SUFFIX对应 2,应用宏_output_2();

    当有3个参数时,SUFFIX对应 4,应用宏_output_4();

    也就是说,当有 x个参数,2^(n-1) < x <= 2^ n,时,我们应用 _output_[2^n]() 这个宏,

    并逐步一分为二,扩展开来,最终扩展到剩下一个参数时,则把这个参数打印出来。

           这是我们设想一个的理想的状态,实际编程中编译器会提示错误。因为,比如5个参数时,

    output('1', '2', '3', '4', '5')扩展为:

    _output8('1', '2', '3', '4', '5') => _output4('1', '2', '3', '4') _output4('5') 

    这时编译器会提示_output4('5') 存在错误。只传入一个参数,不能匹配有两个固定参数的宏_output4(_1, _2, ...) 。也许你会说那就_output4(_1, _2, ...) 改成 _output4(_1, ...)。但这样一个就没法把4个参数时拆分成 '1','2' 和 '3','4'两拨了。就无法实现让子规模减半了。

           聪明的你,也许已经想到了。那就把 _output4('5') 改写成  _output4('5', "") , 多了一个空串("")?没关系,反正最后输出空串(cout << "")也没改变什么。其实不然,你看_output2宏,后面会多输出一个 ',' 。也许你觉得行尾多输出一个','也没什么。其实不然,看有10个参数时的扩展

    1. #define _output_1 (_1, ...) cout << _1 << ", ";
    2. #define _output_2 (_1, ...) _output_1(_1) _output_1(__VA_ARGS__)
    3. #define _output_4 (_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__)
    4. #define _output_8 (_1, _2, _3, _4, ...) \
    5. _output_4 (_1, _2, _3, _4) _output_4(__VA_ARGS__, "")
    6. #define _output_16(_1, _2, _3, _4, _5, _6, _7, _8, ...) \
    7. _output_8 (_1, _2, _3, _4, _5, _6, _7, _8) _output_8(__VA_ARGS__, "", "", "")
    8. _output('1', '2', ... ,'10')
    9. => _output_16('1', '2', ... ,'10')
    10. => _output_8('1', '2', ... ,'8') _output_8('9', '10', "", "", ""
    11. => _output_8('1', '2', ... ,'8') _output_4('9', '10', "", "") _output_4("", "")
    12. => _output_8('1', '2', ... ,'8') _output_2('9', '10') _output_2("","") _output_2("","")
    13. => _output_8('1', '2', ... ,'8') _output_2('9', '10') \
    14. _output_1(""), _output_1(""), _output_1(""), _output_1("")

           这后面多了4个_output_1(""), 就会多输出4个 ","。这已经不是你能忍受的了。目前我没有较好的办法来解决这个问题。只能在最后_output_1("") 时, 判断 参数=="" 时,不输出 ","。

    1. #ifdef __GNUC__
    2. #define _output_1(_1, ...) if (string(typeid(_1).name()) != "A1_c") cout << ", " << _1;
    3. // typeid("").name()) = "A1_c" // 环境 gcc (GCC) 10.2.0
    4. #elif defined(__MSC_VER__)
    5. #define _output_1(_1, ...) if (!(string(typeid(_1).name()) == "const char*" && _1 == "")) cout << ", " << _1;
    6. // typeid("").name()) = "const char*" // 环境 VS2010
    7. #endif

    基于上面的构造形式,在打印多个参数时,最后行尾总会留下一个","

    那么,我们可以把参数数列拆分为如:

    1   ,2 ,3    ,4 ,5 ,6 ,7    ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15

    先打出 1: cout << 1;再打出其它:_output(_1) cout << " ," << _1

    以下我贴出完整代码:

    1. // 可变参数宏打印 (Variadic Macros "__VA_ARGS__")
    2. // 虽然宏不可以递归和重载,但我发明了“倍增宏扩展”方式。
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. // vs里,如果 ... 表示的内容为空,__VA_ARGS__ 也为空,但 __VA_ARGS__仍占一个参数位置
    8. // GCC里 上面的情况不会出现,能正常推导宏
    9. // output 最多可输入16+1=17个参数
    10. #define NONE_ARGS ", None Args!\n"
    11. #define ARGS_OVERFLOW ", Arg's count overflow!\n"
    12. #define _output_() cerr << NONE_ARGS; cout << NONE_ARGS;
    13. #define _output_N(...) cerr << ARGS_OVERFLOW; cout << ARGS_OVERFLOW;
    14. #define _RULER_16(PREFIX, _N, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    15. #define _output_0()
    16. #define _output_1(_1, ...) if (typeid(_1) != typeid("")) cout << ", " << _1;
    17. #define _output_2(_1, ...) _output_1(_1) _output_1(__VA_ARGS__)
    18. #define _output_4(_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__, "")
    19. #define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) _output_4(__VA_ARGS__, "", "")
    20. #define _output_16(_1, _2, _3, _4, _5, _6, _7, _8, ...) _output_8(_1, _2, _3, _4, _5, _6, _7, _8) _output_8(__VA_ARGS__, "", "", "", "")
    21. #define output(_1, ...) cout << _1; _RULER_16 (_output, ## __VA_ARGS__, N, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1, 0)(__VA_ARGS__)
    22. #define outputLn(...) output(__VA_ARGS__) cout << endl;
    23. int main() {
    24. cout << sizeof("") << " " << typeid("").name() << " " << typeid(typeof "").name() << endl;
    25. outputLn(1);
    26. outputLn(1, 2.0f);
    27. outputLn(1, 2.0f, 'a');
    28. outputLn(1, 2.0f, 'a', 3e-2);
    29. outputLn(1, 2.0f, 'a', 3e-2, "UFO");
    30. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u);
    31. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060);
    32. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1);
    33. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101);
    34. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l);
    35. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l);
    36. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l);
    37. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful);
    38. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull);
    39. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 3.14e-1);
    40. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 3.14e-1, 9876543210llu);
    41. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 3.14e-1, 9876543210llu, '\0');
    42. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 3.14e-1, 9876543210llu, '\0', 0.0);
    43. return 0;
    44. }
    45. /*
    46. 输出:
    47. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832
    48. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159
    49. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565
    50. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375
    51. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890
    52. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890, 0.314
    53. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890, 0.314, 9876543210
    54. 1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890, 0.314, 9876543210,
    55. 1, Arg's count overflow!
    56. */

           最后,总结一下复杂度,假设要打印 N = 2^n个不同类型的参数。

           采用幂增宏则需要写O(logN)行宏,每行最长O(3N/2)个参数,3个宏名.

           采用层叠宏则需要写O(N)行宏, 每个行最长O(4)个参数,2个宏名。

           以宏名长度为20(#define _output_1024),参数长度7(, _1024)计算。

    相比

    1. logN * (3*20 + 7 * 3N/2)  V.S  N * (2*20 + 7 * 4)
    2. 60logN + logN * 21N/2 V.S 68*N
    3. (120+21N) * logN V.S 136*N
    4. (120 + 21*2^n) * n V.S 136 * 2^n
    5. 120n + 21n * 2^n V.S 136 * 2^n
    6. f(n) = 120n + 21n * 2^n - 136 * 2^n
    7. 当f(n) > 0, 左边复杂度 > 右边复杂度;
    8. 当f(n) < 0, 左边复杂度 < 右边复杂度

       当n >= 6时, "幂增宏"复杂度都永远大于"层叠宏"。但其实像

    _RULER_16(PREFIX, _N, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...)

    这样的长串,身为程序员,我们可以先写个函数,打印出 “_16, _15, ... _0”,再复制粘贴上去就OK了,或者创建个宏来表示。记得以前看过类似的,boost库把这长串用verctor容器装载起来了,但目前要找到相应的代码有点困难,希望有人能给我留言,告诉我是boost的哪个文件。至此,“完美”~~解决“可变参数宏”打印方案。

           上述代码中提到

    // vs里,如果 ... 表示的内容为空,__VA_ARGS__ 也为空,但 __VA_ARGS__仍占一个参数位置

    这难道就是数学上的“诡辩”: 空集非空!

    在vs2010下测试,

    outputLn(1) 会展开成 cout << 1; cout << ' ,' << ; //会报错

    就是说即使没有第2个参数,__VA_ARGS__ 也为空,但 __VA_ARGS__仍占一个参数位置

    SUFFIX被推到 SUFFIX=1 的位置。

    解决方法是,在一开始就给加个末尾空串 "",让__VA_ARGS__ 不为空。

    1. #define _output_1(_1, ...) if (not_equal(_1, "")) cout << " ," << _1;
    2. //... ...
    3. #define outputAdd(...) _RULER_16 (_output, ## __VA_ARGS__, N, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1, 0)(__VA_ARGS__)
    4. #define output(...) outputAdd(__VA_ARGS__, "")
    5. #define outputLn(...) output(__VA_ARGS__) cout << endl;

    第二个方法是按

    1, 2, 3, 4,    5, 6,    7, 这样的方案打印参数序列,即末尾会多出一个","。主要代码如下:

    1. #define _output_1(_1, ...) if (not_equal(_1, "")) cout << _1 << ", ";
    2. //... ...
    3. #define output(...) _RULER_16 (_output, ## __VA_ARGS__, N, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1, 0)(__VA_ARGS__)
    4. #define outputLn(...) output(__VA_ARGS__) cout << endl;

    第三个方法比较“银蛋”了。参考 expand_ 会把宏参数会先尽可能展开后再进行替换。

    1. #define expand_(...) __VA_ARGS__
    2. #define bench_pattern(...) "",##__VA_ARGS__, "" // 当参数为空,不是吃掉逗号,而是把逗号换成空格
    3. #define second__(_1, _2, ...) _2
    4. #define second_(...) expand_(second__(__VA_ARGS__))
    5. #define bench_first(...) second_(bench_pattern(__VA_ARGS__)) // 模式: 取第1个参数,若为空,返回 ""
    6. #define _1st bench_first

           造一个串  "", ##__VA_ARGS__, "", 当参数为空,取_2得 "", 当参数不为空,则取_2,实则取参数列表里第一个参数。实测得_1st()="", _1st(1)=1,_1st(1,2)=1。

    expand()顺带把用__VA_ARGS__传参数时不能分割出前n个参数的问题解决了。只要传递给下一个宏前,把想要扩展的__VA_ARGS__用expand()套住。可以在最外层套,比如expand(your_macro(__VA_ARGS__))。最后贴上在gcc 10.0,vs2010,vs2019下测试通过的代码。

    1. // output 最多可输入16+1=17个参数
    2. #if defined(_MSC_VER) && (_MSC_VER >= 1600) // >= vs2010, 静态断言需要C++11标准支持,但在vs2019下要求我开启 "/std:c++17"
    3. #define CAN_STATIC_ASSERT
    4. #elif defined(__GNUC__) && (GCC_VERSION >= 40300)// >= gcc 4.3.0
    5. #define CAN_STATIC_ASSERT
    6. #endif
    7. // #define _MSC_VER 1600
    8. #ifdef CAN_STATIC_ASSERT
    9. #define _output_() static_assert("None Args!" && 0);
    10. #define _output_N(...) static_assert("Arg's count overflow!" && 0);
    11. #else
    12. #define _output_() assert("None Args!" && 0);
    13. #define _output_N(...) assert("Arg's count overflow!" && 0);
    14. #endif
    15. // 这里利用的原理是:
    16. // (1) expand_ 会把宏参数会先尽可能展开后再进行替换。
    17. // (2) 遇到#或##时,其相连的宏参数不会展开,然而这不意味着这个宏参数本身不会展开,其他部分用到这个宏参数的地方还是会展开的。
    18. // (3) 对当前宏展开完成后不会重新扫描一遍当前字符串,但可以另起一个宏,嵌套当前宏,让另一个宏去做扫描
    19. // boost-preprocessor (github)
    20. // https://www.codenong.com/cs106877864/
    21. //
    22. #if defined(_MSC_VER) && (_MSC_VER < 1920) // < vs2019
    23. //
    24. #define expand_(...) __VA_ARGS__
    25. #define E_ expand_
    26. //
    27. #define bench_pattern(...) "", ## __VA_ARGS__, "" // 当参数为空,不是吃掉逗号,而是把逗号换成空格
    28. #define bench_pattern2(...) bench_pattern(__VA_ARGS__)// 再套一层,可去掉替换逗号后的空格
    29. #define second__(_1, _2, ...) _2
    30. #define second_(...) expand_(second__(__VA_ARGS__))
    31. #define bench_first(...) second_(bench_pattern(__VA_ARGS__)) // 模式: 取第1个参数,若为空,返回 ""
    32. #define _1st bench_first
    33. //
    34. #define rest__(_1, ...) __VA_ARGS__
    35. #define rest_(...) expand_(rest__(__VA_ARGS__))
    36. #define _rst rest_
    37. //
    38. #define _ruler_16(PREFIX, _N, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    39. #define _output_0()
    40. #define _output_x(_1) if (typeid(_1) != typeid("")) cout << ", " << _1;
    41. #define _output_1(...) _output_x(_1st(__VA_ARGS__))
    42. #define _output_2(_1, ...) _output_1(_1) _output_1(__VA_ARGS__)
    43. #define _output_4(_1, _2, ...) _output_2(_1, _2) E_(_output_2(__VA_ARGS__, ""))
    44. #define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) E_(_output_4(__VA_ARGS__, "", ""))
    45. #define _output_16(_1, _2, _3, _4, _5, _6, _7, _8, ...) _output_8(_1, _2, _3, _4, _5, _6, _7, _8) E_(_output_8(__VA_ARGS__, "", "", "", ""))
    46. // outputQ(), 空参数仍会占用1个参数位置,但 _1st()会把空参数转为 ""
    47. #define outputQ(...) E_(_ruler_16(_output, ## __VA_ARGS__, N, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1, 0)(__VA_ARGS__))
    48. #define output(...) cout << _1st(__VA_ARGS__); outputQ(_rst(__VA_ARGS__))
    49. #define outputLn(...) output(__VA_ARGS__) cout << endl
    50. //
    51. #else // _MSC_VER >= vs2019
    52. #define _ruler_16(PREFIX, _N, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
    53. #define _output_0()
    54. #define _output_1(_1, ...) if (typeid(_1) != typeid("")) cout << ", " << _1;
    55. #define _output_2(_1, ...) _output_1(_1) _output_1(__VA_ARGS__, "")
    56. #define _output_4(_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__, "")
    57. #define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) _output_4(__VA_ARGS__, "", "")
    58. #define _output_16(_1, _2, _3, _4, _5, _6, _7, _8, ...) _output_8(_1, _2, _3, _4, _5, _6, _7, _8) _output_8(__VA_ARGS__, "", "", "", "")
    59. #define output(_1, ...) cout << _1; _ruler_16(_output, ## __VA_ARGS__, N, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1, 0)(__VA_ARGS__)
    60. #define outputLn(...) output(__VA_ARGS__) cout << endl
    61. #endif
    62. int main() {
    63. #if defined(_MSC_VER) && (_MSC_VER < 1920) // < vs2019
    64. #define _9876543210ULL 9876543210Ui64
    65. #else
    66. #define _9876543210ULL 9876543210llu
    67. #endif
    68. // outputLn();
    69. outputLn(1);
    70. outputLn(1, 2.0f);
    71. outputLn(1, 2.0f, 'a');
    72. outputLn(1, 2.0f, 'a', 3e-2);
    73. outputLn(1, 2.0f, 'a', 3e-2, "UFO");
    74. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u);
    75. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060);
    76. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1);
    77. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear");
    78. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll);
    79. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l);
    80. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l);
    81. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful);
    82. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull);
    83. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 314159E-5L);
    84. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 314159E-5L, _9876543210ULL);
    85. outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 314159E-5L, _9876543210ULL, '\0');
    86. // outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 314159E-5L, _9876543210ULL, '\0', 0.0);
    87. return 0;
    88. }

    好,时间到,下课!同学们记得做课后作业 :假设让你来设计,按斐波那契数列设计一个倍增宏。你能否写出来?

  • 相关阅读:
    大数据基础设施搭建 - ZooKeeper
    【面经】讲下spring bean的三级缓存与循环依赖问题
    50天50个前端小项目(纯html+css+js)第十八天(背景轮播图)
    JAVAWeb1:登录页面
    react: antd组件使用 FC Fragment
    MXNet对NIN网络中的网络的实现
    Objective-C中的KVO
    【小沐学QT】QT学习之OpenGL开发笔记
    Linux操作系统:函数、CronTab及定时任务和站点可用性监测
    【前端面试必知】对Vue.observable的了解
  • 原文地址:https://blog.csdn.net/tiandyoin/article/details/126458052