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

本文介绍了如何在C99中利用可变参数宏(__VA_ARGS__)模拟C++11的可变参数模板功能,讨论了在处理不同数量和类型参数时遇到的问题及解决方案,包括利用宏循环、模板特化和函数重载等技术,旨在提供一种在C99中方便地打印或处理不同类型变量的方法。

      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,很遗憾的告诉你,必须要求所有参数类型一样。否则,会对短字节类型做向上提升。

#define printList(Func, type, cnt, ...)      \
  {                                          \
    va_list ls;                              \
    va_start(ls, cnt);                       \
    for (int i = 0; i < cnt; i++) {          \
      cout << Func(va_arg(ls, type)) << " "; \
    }                                        \
    cout << endl;                            \
  }

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

  template<class T>
  void printType(T &t) {cout << typeid(t).name() << endl;}
  
  template<class T, class... Args>
  void printType(T &t, Args&... rest) {
      cout << typeid(t).name() << ', ';
      printType(rest...);
  } 

// printType('a', 1, 2.0, "hello");
// 输出:
// char, int, double, const char*

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

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

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

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

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

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

#define _SELECT(PREFIX, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
#define _print_1(fmt) printf(fmt "\n")
#define _print_N(fmt, ...) printf(fmt "\n", ## __VA_ARGS__)
#define println(...) _SELECT(_print, ## __VA_ARGS__, N, N, N, N, 1)(__VA_ARGS__)

// 若要自定义处理宏。如自定义2个参数时的处理宏,
// 则要添加_print_2()和修改 println 倒数第 1 个N为 2 ,参考my_println()。
#define _print_2(fmt, ...) printf(fmt "\n", ## __VA_ARGS__)
#define my_println(...) _SELECT(_print, ## __VA_ARGS__, N, N, N, 2, 1)(__VA_ARGS__)

int main1(int argc, char *argv[]) {
  println("here is a log message");
  println("here is a log message with a param: %d", 42);
  my_println("here is a log message with a param: %d", 42);
  my_println("here is a log message with a param: %d %d %s %d", 42, 33, "abc", 13);
  return 0;
}

// _SELECT(PREFIX, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
// _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 个不同参数(个数可变)的调用方式呢?下面给出一个例子:

#define _SELECT_10(PREFIX, _N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX

template<typename T>
void _output_0(T _1){
  cout << _1 << ", ";
}
void _output_0(){}

#define _output_() cout << "None Args!" << endl;
#define _output_N(...) cout << "Args' count overflow!\n";

#define _output_1(_1) cout << _1;
#define _output_2(_1, ...) _output_1(_1) cout << ", "; _output_0(__VA_ARGS__);
#define _output_4(_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__)
#define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) _output_4(__VA_ARGS__)

#define output(...) _SELECT_10(_output, ##__VA_ARGS__, N, 16, 16, 8, 8, 8, 8, 4, 4, 2, 1)(__VA_ARGS__)
#define outputLn(...) output(__VA_ARGS__) cout << endl;

// output(1) => SUFFIX=1 => _output_1()
// output(1, 2) => SUFFIX=2 => _ouput_2(1, 2) => _output_1(1) cout<<","; _output0(2) 
// output(1, 2, 3) => SUFFIX=4 => _output_4(1, 2, 3) => _output_2(1, 2) _output_2(3)
//    => _output_2(1, 2) _output_1(1) cout < ","; _output_0();
//    => _output_1(1)  cout < ","; _output_0(2); _output_1(1) cout < ","; _output_0();

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

// 0 个参数时:
_SELECT_10(PREFIX, 
_N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
_SELECT_10(_output, 
 N,  16, 16,  8,  8,  8,  8,  4,  4,  2,  1)() => _output_

// 1 个参数时:
_SELECT_10(PREFIX, 
_N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
_SELECT_10(_output, 
'1',  N, 16, 16,  8,  8,  8,  8,  4,  4,  2, 1 ) ('1') => _output_1('1')

// 2 个参数时:
_SELECT_10(PREFIX, 
_N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
_SELECT_10(_output, 
'1','2',  N, 16, 16,  8,  8,  8,  8,  4,  4, 2,      1 ) ('1','2')
                                            => _output_2 ('1','2')

// 3 个参数时:
_SELECT_10(PREFIX, 
_N, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
_SELECT_10(_output, 
'1','2','3',  N, 16, 16,  8,  8,  8,  8,  4, 4,      2, 1 ) ('1','2','3')
                                               => _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个参数时的扩展

#define _output_1 (_1, ...) cout << _1 << ", ";
#define _output_2 (_1, ...) _output_1(_1) _output_1(__VA_ARGS__)
#define _output_4 (_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__)
#define _output_8 (_1, _2, _3, _4, ...) \
        _output_4 (_1, _2, _3, _4) _output_4(__VA_ARGS__, "")
#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__, "", "", "")

_output('1', '2', ... ,'10') 
=> _output_16('1', '2', ... ,'10')
=> _output_8('1', '2', ... ,'8') _output_8('9', '10', "", "", "") 
=> _output_8('1', '2', ... ,'8') _output_4('9', '10', "", "") _output_4("", "")
=> _output_8('1', '2', ... ,'8') _output_2('9', '10') _output_2("","") _output_2("","")
=> _output_8('1', '2', ... ,'8') _output_2('9', '10') \
   _output_1(""), _output_1(""), _output_1(""), _output_1("")

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

#ifdef __GNUC__
#define _output_1(_1, ...) if (string(typeid(_1).name()) != "A1_c") cout << ", " << _1;

// typeid("").name()) = "A1_c" // 环境 gcc (GCC) 10.2.0
#elif defined(__MSC_VER__)
#define _output_1(_1, ...) if (!(string(typeid(_1).name()) == "const char*" && _1 == "")) cout << ", " << _1;

// typeid("").name()) = "const char*" // 环境 VS2010
#endif

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

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

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

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

以下我贴出完整代码:

// 可变参数宏打印 (Variadic Macros "__VA_ARGS__")
// 虽然宏不可以递归和重载,但我发明了“倍增宏扩展”方式。
#include <stdio.h>

#include <iostream>
#include <string>
using namespace std;

// vs里,如果 ... 表示的内容为空,__VA_ARGS__ 也为空,但 __VA_ARGS__仍占一个参数位置
// GCC里 上面的情况不会出现,能正常推导宏
// output 最多可输入16+1=17个参数
#define NONE_ARGS ", None Args!\n"
#define ARGS_OVERFLOW ", Arg's count overflow!\n"
#define _output_() cerr << NONE_ARGS; cout << NONE_ARGS;
#define _output_N(...) cerr << ARGS_OVERFLOW; cout << ARGS_OVERFLOW;
#define _RULER_16(PREFIX, _N, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
#define _output_0()
#define _output_1(_1, ...) if (typeid(_1) != typeid("")) cout << ", " << _1;
#define _output_2(_1, ...) _output_1(_1) _output_1(__VA_ARGS__)
#define _output_4(_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__, "")
#define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) _output_4(__VA_ARGS__, "", "")
#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__, "", "", "", "")
#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__)
#define outputLn(...) output(__VA_ARGS__) cout << endl;

int main() {
  cout << sizeof("") << " " << typeid("").name() << " " << typeid(typeof "").name() << endl;
 
  outputLn(1);

  outputLn(1, 2.0f);
  outputLn(1, 2.0f, 'a');
  outputLn(1, 2.0f, 'a', 3e-2);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO");

  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101);

  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful);

  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 3.14e-1);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 3.14e-1, 9876543210llu);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, 0b110101, 9283832l, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 3.14e-1, 9876543210llu, '\0');

  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);
  return 0;
}

/*
输出:
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890, 0.314
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890, 0.314, 9876543210
1, 2, a, 0.03, UFO, 18, 48, 241, 53, 9283832, -3.14159, 74565, 184455375, 1234567890, 0.314, 9876543210, 
1, Arg's count overflow!
*/

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

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

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

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

相比

logN * (3*20 + 7 * 3N/2)  V.S  N * (2*20 + 7 * 4)
60logN + logN * 21N/2     V.S 68*N
(120+21N) * logN          V.S 136*N
(120 + 21*2^n) * n        V.S 136 * 2^n
120n + 21n * 2^n          V.S 136 * 2^n
f(n) = 120n + 21n * 2^n - 136 * 2^n
当f(n) > 0, 左边复杂度 > 右边复杂度;
当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__ 不为空。

#define _output_1(_1, ...) if (not_equal(_1, "")) cout << " ," << _1;
//... ...
#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__)
#define output(...) outputAdd(__VA_ARGS__, "")
#define outputLn(...) output(__VA_ARGS__) cout << endl;

第二个方法是按

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

#define _output_1(_1, ...) if (not_equal(_1, "")) cout << _1 << ", ";
//... ...
#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__)
#define outputLn(...) output(__VA_ARGS__) cout << endl;

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

#define expand_(...) __VA_ARGS__
#define bench_pattern(...) "",##__VA_ARGS__, ""       // 当参数为空,不是吃掉逗号,而是把逗号换成空格
#define second__(_1, _2, ...) _2
#define second_(...) expand_(second__(__VA_ARGS__))
#define bench_first(...) second_(bench_pattern(__VA_ARGS__)) // 模式: 取第1个参数,若为空,返回 ""
#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下测试通过的代码。

// output 最多可输入16+1=17个参数
#if defined(_MSC_VER) && (_MSC_VER >= 1600) // >= vs2010, 静态断言需要C++11标准支持,但在vs2019下要求我开启 "/std:c++17"
  #define CAN_STATIC_ASSERT
#elif defined(__GNUC__) && (GCC_VERSION >= 40300)// >= gcc 4.3.0
  #define CAN_STATIC_ASSERT
#endif
 
// #define _MSC_VER 1600

#ifdef CAN_STATIC_ASSERT
#define _output_() static_assert("None Args!" && 0);
#define _output_N(...) static_assert("Arg's count overflow!" && 0);
#else
#define _output_() assert("None Args!" && 0);
#define _output_N(...) assert("Arg's count overflow!" && 0);
#endif

// 这里利用的原理是: 
// (1) expand_ 会把宏参数会先尽可能展开后再进行替换。
// (2) 遇到#或##时,其相连的宏参数不会展开,然而这不意味着这个宏参数本身不会展开,其他部分用到这个宏参数的地方还是会展开的。
// (3) 对当前宏展开完成后不会重新扫描一遍当前字符串,但可以另起一个宏,嵌套当前宏,让另一个宏去做扫描
// boost-preprocessor (github)
// https://www.codenong.com/cs106877864/
//
#if defined(_MSC_VER) && (_MSC_VER < 1920) // < vs2019
//
#define expand_(...) __VA_ARGS__
#define E_ expand_
// 
#define bench_pattern(...) "", ## __VA_ARGS__, ""       // 当参数为空,不是吃掉逗号,而是把逗号换成空格
#define bench_pattern2(...) bench_pattern(__VA_ARGS__)// 再套一层,可去掉替换逗号后的空格
#define second__(_1, _2, ...) _2
#define second_(...) expand_(second__(__VA_ARGS__))
#define bench_first(...) second_(bench_pattern(__VA_ARGS__)) // 模式: 取第1个参数,若为空,返回 ""
#define _1st bench_first
//
#define rest__(_1, ...) __VA_ARGS__
#define rest_(...) expand_(rest__(__VA_ARGS__))
#define _rst rest_
//
#define _ruler_16(PREFIX, _N, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
#define _output_0()
#define _output_x(_1) if (typeid(_1) != typeid("")) cout << ", " << _1;
#define _output_1(...) _output_x(_1st(__VA_ARGS__))
#define _output_2(_1, ...) _output_1(_1) _output_1(__VA_ARGS__)
#define _output_4(_1, _2, ...) _output_2(_1, _2) E_(_output_2(__VA_ARGS__, ""))
#define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) E_(_output_4(__VA_ARGS__, "", ""))
#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__, "", "", "", ""))
//      outputQ(), 空参数仍会占用1个参数位置,但 _1st()会把空参数转为 ""
#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__))
#define output(...) cout << _1st(__VA_ARGS__); outputQ(_rst(__VA_ARGS__))
#define outputLn(...) output(__VA_ARGS__) cout << endl
//
#else // _MSC_VER >= vs2019
#define _ruler_16(PREFIX, _N, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, SUFFIX, ...) PREFIX ## _ ## SUFFIX
#define _output_0()
#define _output_1(_1, ...) if (typeid(_1) != typeid("")) cout << ", " << _1;
#define _output_2(_1, ...) _output_1(_1) _output_1(__VA_ARGS__, "")
#define _output_4(_1, _2, ...) _output_2(_1, _2) _output_2(__VA_ARGS__, "")
#define _output_8(_1, _2, _3, _4, ...) _output_4(_1, _2, _3, _4) _output_4(__VA_ARGS__, "", "")
#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__, "", "", "", "")
#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__)
#define outputLn(...) output(__VA_ARGS__) cout << endl
#endif


int main() {

#if defined(_MSC_VER) && (_MSC_VER < 1920) // < vs2019
#define _9876543210ULL 9876543210Ui64
#else
#define _9876543210ULL 9876543210llu
#endif

  // outputLn();
  outputLn(1);

  outputLn(1, 2.0f);
  outputLn(1, 2.0f, 'a');
  outputLn(1, 2.0f, 'a', 3e-2);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO");

  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear");

  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful);

  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 314159E-5L);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 314159E-5L, _9876543210ULL);
  outputLn(1, 2.0f, 'a', 3e-2, "UFO", 18u, 060, 0xF1, "hello, " "d" "ear", 9283832ll, -3.1415926l, 0x12345l, 0xafe90cful, 1234567890ull, 314159E-5L, _9876543210ULL, '\0');

  // 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);
  
  return 0;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值