可变参数模板的应用

引入

在C++中经常打印变量来调试代码，但无论是printf还是cout总是很麻烦：

printf

Copy
int a = 1;
float b = 2.0;
char c = 'c';
printf("a = %d, b = %f, c = %c", a, b, c);

cout

Copy
int a = 1;
float b = 2.0;
char c = 'c';
std::cout << "a = " << a << ", b = " << b << ", c = " << c << '\n';

可变参数宏

可变参数宏是C99引入的一个特性，C++11开始支持。

Copy
#define def_name(...) def_body(__VA_ARGS__)

可变参数模板

C++11允许模板定义有任意类型任意数量的模板参数（包括 $0$ 个模板参数）。

Copy
template<typename... Values> class tuple;

如果不希望有 $0$ 个模板实参，可以如下声明：

Copy
template<typename First, typename... Rest> class tuple;

可变参数模板适用于函数模板，例如printf函数的实现：

Copy
template<typename... Args>
void printf(const std::string &str_format, Args... args);

参数包的展开

对于模板函数

递归展开

Copy
// 递归终止函数
void print() {}

template<typename T, typename... Args>
void print(T head, Args... args) {
std::cout << head << (sizeof...(args) > 0 ? ", " : "");
print(args...);
}

逗号表达式展开：这种方式有点繁琐，不太好理解，不推荐

对于模板类

递归展开

继承展开

宏运算符

#：将宏参数转换为字符串

Copy
#include <stdio.h>
#define stringer( x ) printf( #x "\n" )
int main() {
stringer( In quotes in the printf function call );
stringer( "In quotes when printed to the screen" );
stringer( "This: \" prints an escaped double quote" );
}

使用gcc -E预处理之后，得到如下结果

Copy
int main() {
printf("In quotes in the printf function call" "\n");
printf("\"In quotes when printed to the screen\"" "\n");
printf("\"This: \\\" prints an escaped double quote\"" "\n");
}

#@：将参数用单引号括起来，作为一个字符处理

Copy
#define makechar(x) #@x
c = makechar(b);

预处理之后得到：

Copy
c = 'b';

##：将两个独立的token连接在一起

Copy
#define paster(n) printf("token" #n " = %d", token##n)
int token9 = 9;
paster(9);

预处理之后得到：

Copy
printf("token9 = %d", token9);

实现DBG宏

掌握了以上技能，我们就可以实现一个简单的调试宏：

Copy
#include <iostream>
#include <string>

template <typename T>
void _DBG(const char *s, T t) {
std::cout << s << "=" << t << '\n';
}

template <typename T, typename... Args>
void _DBG(const char *s, T t, Args... args) {
while (*s != ',')
std::cout << *s++;
std::cout << "=" << t << ",";
_DBG(s + 1, args...);
}

#define DBG(...) _DBG(#__VA_ARGS__, __VA_ARGS__)

int main() {
int a = 1;
float b = 2.0;
char c = 'c';
std::string d = "def";
DBG(a, b, c, d);
return 0;
}

上面的代码输出：

Copy
a=1, b=2, c=c, d=def

References

https://oi-wiki.org/lang/new/#_9

https://zh.wikipedia.org/wiki/%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0%E6%A8%A1%E6%9D%BF

https://docs.microsoft.com/en-us/cpp/preprocessor/stringizing-operator-hash?view=msvc-170