Unverified Commit 70a3cb0d authored by Shuhao Zhang's avatar Shuhao Zhang Committed by GitHub
Browse files

Merge pull request #2538 from Backl1ght/patch-3

增加c++11新特性可变参数模板相关内容
parents 2ce2a53a b3c48574
Loading
Loading
Loading
Loading
+159 −0
Original line number Diff line number Diff line
@@ -310,8 +310,167 @@ int main() {
}
```

## 可变参数宏

可变参数宏是 C99 引入的一个特性,C++ 从 C++11 开始支持这一特性。可变参数宏允许宏定义可以拥有可变参数,例如:

```cpp
#define def_name(...) def_body(__VA_ARGS__)
```

其中, `...` 是缺省符号, `__VA_ARGS__` 在调用时会替换成实际的参数列表, `def_body` 应为可变参数模板函数。

现在就可以这么调用 `def_name`

```cpp
def_name();
def_name(1);
def_name(1, 2, 3);
def_name(1, 0.0, "abc");
```

## 可变参数模板

在 C++11 之前,类模板和函数模板都只能接受固定数目的模板参数。C++11 允许 **任意个数、任意类型** 的模板参数。

### 可变参数模板类

例如,下列代码声明的模板类 `tuple` 的对象可以接受任意个数、任意类型的模板参数作为它的模板形参。

```cpp
template <typename... Values>
class Tuple {};
```

其中, `Values` 是一个模板参数包,表示 0 个或多个额外的类型参数。模板类只能含有一个模板参数包,且模板参数包必须位于所有模板参数的最右侧。

所以,可以这么声明 `tuple` 的对象:

```cpp
Tuple<> test0;
Tuple<int> test1;
Tuple<int, int, int> test2;
Tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> test3;
```

如果要限制至少有一个模板参数,可以这么定义模板类 `tuple`

```cpp
template <typename First, typename... Rest>
class Tuple {};
```

### 可变参数模板函数

同样的,下列代码声明的模板函数 `fun` 可以接受任意个数、任意类型的模板参数作为它的模板形参。

```cpp
template <typename... Values>
void fun(Values... values) {}
```

其中, `Values` 是一个模板参数包, `values` 是一个函数参数包,表示 0 个或多个函数参数。模板函数只能含有一个模板参数包,且模板参数包必须位于所有模板参数的最右侧。

所以,可以这么调用 `fun` 函数:

```cpp
fun();
fun(1);
fun(1, 2, 3);
fun(1, 0.0, "abc");
```

### 参数包展开

之前说面了如何声明模板类或者模板函数,但是具体怎么使用传进来的参数呢?这个时候就需要参数包展开。

对于模板函数而言,参数包展开的方式有递归函数方式展开以及逗号表达式和参数列表方式展开。

对于模板类而言,参数包展开的方式有模板递归方式展开和继承方式展开。

#### 递归函数方式展开参数包

递归函数方式展开参数包需要提供展开参数包的递归函数和参数包展开的终止函数。

举个例子,下面这个代码段使用了递归函数方式展开参数包,实现了可接受大于等于 2 个参数的取最大值函数。

```cpp
// 递归终止函数,可以是0或多个参数。
template <typename T>
T MAX(T a, T b) {
  return a > b ? a : b;
}

// 展开参数包的递归函数
template <typename First, typename... Rest>
First MAX(First first, Rest... rest) {
  return MAX(first, MAX(rest...));
}

// int a = MAX(1); // 编译不通过,但是对1个参数取最大值本身也没有意义
// int b = MAX(1, "abc"); //
// 编译不通过,但是在整数和字符串间取最大值本身也没有意义
int c = MAX(1, 233);              // 233
int d = MAX(1, 233, 666, 10086);  // 10086
```

### 可变参数模板的应用

举个应用的例子,有的人在 debug 的时候可能不喜欢用 IDE 的调试功能,而是喜欢输出中间变量。但是,有时候要输出的中间变量数量有点多,写输出中间变量的代码的时候可能会比较烦躁,这时候就可以用上可变参数模板和可变参数宏。

```cpp
// Author: Backl1ght
#include <bits/stdc++.h>
using namespace std;

namespace DEBUG {
template <typename T>
inline void _debug(const char* format, T t) {
  cerr << format << '=' << t << endl;
}

template <class First, class... Rest>
inline void _debug(const char* format, First first, Rest... rest) {
  while (*format != ',') cerr << *format++;
  cerr << '=' << first << ",";
  _debug(format + 1, rest...);
}

template <typename T>
ostream& operator<<(ostream& os, vector<T> V) {
  os << "[ ";
  for (auto vv : V) os << vv << ", ";
  os << "]";
  return os;
}

#define debug(...) _debug(#__VA_ARGS__, __VA_ARGS__)
}  // namespace DEBUG
using namespace DEBUG;

int main(int argc, char* argv[]) {
  int a = 666;
  vector<int> b({1, 2, 3});
  string c = "hello world";

  // before
  cout << "a=" << a << ", b=" << b << ", c=" << c
       << endl;  // a=666, b=[ 1, 2, 3, ], c=hello world
  // 如果用printf的话,在只有基本数据类型的时候是比较方便的,然是如果要输出vector等的内容的话,就会比较麻烦

  // after
  debug(a, b, c);  // a=666, b=[ 1, 2, 3, ], c=hello world

  return 0;
}
```

这样一来,如果事先在代码模板里写好 DEBUG 的相关代码,后续输出中间变量的时候就会方便许多。

## 参考

1.  [C++ reference](https://en.cppreference.com/) 
2.  [C++ 参考手册](https://zh.cppreference.com/) 
3.  [C++ in Visual Studio](https://docs.microsoft.com/en-us/cpp/overview/visual-cpp-in-visual-studio?view=vs-2019) 
4.  [Variadic template](https://en.wikipedia.org/wiki/Variadic_template) 
5.  [Variadic macros](https://en.wikipedia.org/wiki/Variadic_macro)