C++ 初始化列表展开,详细解释这个代码是什么意思？
template<typename T, typename... Ts>
auto printf3(T value, Ts... args) {
    std::cout << value << std::endl;
    (void) std::initializer_list<T>{([&args] {
        std::cout << args << std::endl;
    }(), value)...};
}
## 代码意图
```
template<typename T, typename... Ts>
auto printf3(T value, Ts... args) {
    std::cout << value << std::endl;
    (void) std::initializer_list<T>{([&args] {
        std::cout << args << std::endl;
    }(), value)...};
}
```

printf3 函数的意图是将参数打印出来，一行一个。比如
```
printf3(1, 2, 3, "HelloWorld");
```
的 输出结果为
```
1
2
3
HelloWorld
```

printf3第二行有点难懂，它同时结合了 C++ 的几个语法点

- 初始化列表 initializer_list
- 可变参数模版，Variadic Template
- lambda 表达式
- 逗号表达式

## 初始化列表

初始化列表，还是比较好 懂的，比如下面代码

```
auto tmp = {1, 2, 3, 4};
for (auto& v : tmp) {
    printf("%d\n", v);
}
```

tmp 的类 型就是 std::initializer_list<int>。初始化列表的值不能包含不同类型，下面代码会编译错误

```
auto tmp = {"Hello", 1, 2, 3}; // error
```

也不能是 void值，下面代码也会编译错误

```
auto tmp = {void, void, void}; // error
```

##  可变参数模版

可变参数模版，就是模版中 typename... Ts ，和 Ts... args 这种语法。

```
template<typename T, typename... Ts>
auto printf3(T value, Ts... args)
```

可以将多个输入参数打包（Parameter Pack）到 args 中。比如 printf3(1, 2, 3, 4);，args 就是 2、3、4 三个参数 pack 到 args 中。在模板代码中，可以通过 .... 的语法将参数展开（Parameter Pack Expansion） ，比如下面代码

```
template <typename... Ts>
void doSomething(Ts... args) {
    auto tmp = {args...};
    for (auto &v : tmp) {
        printf("%d\n", v);
    }
}
```

调用 doSomething(1, 2, 3);时，tmp 会展开成

```
auto tmp = {1, 2, 3};
```

args...这种只是简单用法。模板参数还可以在更复杂的表达式中展开，比如

```
auto tmp = { (args * args)...};
auto tmp1 = { std::sin(args)...};
auto tmp2 = { (std::sin(args) + args)...};
```

调用 doSomething(1, 2, 3);时，tmp 会展开成
```
auto tmp = {(1 * 1),(2 * 2),(3 * 3)};
auto tmp1 = { std::sin(1), std::sin(2), std::sin(3) };
auto tmp2 = { (std::sin(1) + 1), (std::sin(2) + 2), (std::sin(2) + 2) };
```

C++ 碰到 ...，就会将包含 args 的表达式展开，可以在很复杂的表达式中展开。

## lambda 表达式

下面的 fn 就是先定义了 lambda 表达式，之后再进行调用
```
int a = 0;
auto fn = [&a] {
    std::cout << a << std::endl;
};
fn();
```

也可以将 lambda 表达式定义和调用 写在一起。比如

```
int a = 0;
auto b = [&a] {
    return a;
}();
```

## 逗号表达式

这个也比较简单。C++ 中 ，假如表达式用逗号分隔，会以此求值，最后一个值就是表达式的值。比如

```
auto a = (funA(), funB(), funC());
```

会以此求值 funA()、funB()、funC()，最终 a 为 funC 的值。

## 拆解原始代码

对几个语法点简单了解后，我们现在来拆解
```
(void) std::initializer_list<T>{([&args] {
    std::cout << args << std::endl;
}(), value)...};
```

首先，里面的复杂表达式
```
([&args] {
    std::cout << args << std::endl;
}(), value)...
```
会按照模板参数依次展开，求值后， 生成一个std::initializer_list<T>。
比如调用为 printf3(1, 2, "HelloWorld"); value 值为 1，args 为 2、"HelloWorld" 两个参数中的打包。将包含 args 的表达式展开，展开的代码大致为
```
void printf3(int value, int arg0, const char *arg1) {
    std::cout << value << std::endl;
    (void)std::initializer_list<int>{
        ([&arg0] {
            std::cout << arg0 << std::endl;
        }(), value),
        
        ([&arg1] {
            std::cout << arg1 << std::endl;
        }(), value)
    };
}
```
arg0 和 arg1 都分别嵌在逗号表达式中，于是分别调用了 lambda 函数，并 value 作为逗号表达式的值。于是 std::initializer_list放的都是 int 类型。
各个模板参数展开，依次调用了 lambda 函数，打印出各自的值。

## 一些细节

- 代码中的  (void)std::initializer_list，这个 (void) 是为了防止编译警告，因为 initializer_list 没有被使用。
- 先定义了 T value，再定义Ts... args，是让 printf3 最少包含一个参数，让 printf3() 调用报编译错误。
- 使用逗号表达式，是 因为代码中，那个 lambda 函数返回值为 void, 而 std::initializer_list<void> 是不合法的。这里也可以让 lambda 返回一个值，省去逗号表达式。比如
```
template<typename T, typename... Ts>
auto printf3(T value, Ts... args) {
  std::cout << value << std::endl;
  (void) std::initializer_list<int>{([&args] {
      std::cout << args << std::endl;
      return 1;
  }())...};
}
```

- 原始代码搞得比较复杂，也为了让 printf3 中的参数可以有不同的类型。比如下面的简单实现，就只能传入相同类型参数。
```
template <typename T, typename... Ts>
auto printf3(T value, Ts... args) {
  auto tmp = {value, args...};
  for (auto &v : tmp) {
      std::cout << v << std::endl;
  }
}
```

- C++17 中有个 Pack fold expression 的语法，可以让实现更简单些
```
template <typename T, typename... Ts>
void printf3(T value, Ts... args) {
  std::cout << value << std::endl;
  ((std::cout << args << std::endl), ...);
}
```