> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/c-17-stl-cook-book/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/c-17-stl-cook-book/chapter4-0-chinese/chapter4-5-chinese.md).

# 使用同一输入调用多个函数

当我们有很多工作要做时，可能就会导致很多代码的重复。使用Lambda表达式就很容易的避免重复代码，并且Lambda表达式将帮助你将这些重复的任务包装起来。

本节，我们将使用Lambda表达式接受一组参数，然后分发给相应的任务函数。这种方式并不需要添加额外的数据结构，所以编译器很容易的将这些函数打包成一个二进制文件(并且没有额外的开销)。

## How to do it...

我们将要完成两个Lambda表达式辅助器，一个能接受一组参数，并调用多个函数对象；另一个使用一个函数调用，引发后续多个函数调用。我们的例子中，我们将使用不同的打印函数打印一些信息出来。

1. 包含打印头文件。

   ```cpp
   #include <iostream>
   ```
2. 首先，让我们实现`multicall`函数，这个函数是本章的重点。这个函数可以接受任意数量的参数，并且返回一个Lambda表达式，这个Lambda表达式只接受一个参数。表达式可以通过这个参数调用所有已提供的函数。这样，我们可以定义`auto call_all (multicall(f, g, h))`函数对象，然后调用`call_all(123)`，从而达到同时调用`f(123); g(123); h(123);`的效果。这个函数看起来比较复杂，是因为我们需要一个语法技巧来展开参数包functions，并在`std::initializer_list`实例中包含一系列可调用的函数对象。

   ```cpp
   template <typename ... Ts>
   static auto multicall (Ts ...functions)
   {
       return [=](auto x) {
           (void)std::initializer_list<int>{
               ((void)functions(x), 0)...
           };
       };
   }
   ```
3. 下一个辅助器能接受一个函数f和一个参数包`xs`。这里要表示的就是参数包中的每个参数都会传入f中运行。这种方式类似于`for_each(f, 1, 2, 3)`调用，从而会产生一系列调用——`f(1); f(2); f(3);`。本质上来说，这个函数使用同样的技巧来为函数展开参数包`xs`：

   ```cpp
   template <typename F, typename ... Ts>
   static auto for_each (F f, Ts ...xs) {
       (void)std::initializer_list<int>{
              ((void)f(xs), 0)...
       };
   }
   ```
4. `brace_print`函数能接受两个字符，并返回一个新的函数对象，这个函数对象可以接受一个参数`x`。其将会打印这个参数，当然会让之前的两个字符将这个参数包围：

   ```cpp
   static auto brace_print (char a, char b) {
       return [=] (auto x) {
           std::cout << a << x << b << ", ";
       };
   }
   ```
5. 现在，我们终于可以在main函数中使用这些定义好的东西了。首先，我们定义函数f，g和h。其使用括号打印函数将其参数进行包围。`nl`函数只打印换行符。

   ```cpp
   int main()
   {
       auto f (brace_print('(', ')'));
       auto g (brace_print('[', ']'));
       auto h (brace_print('{', '}'));
       auto nl ([](auto) { std::cout << '\n'; });
   ```
6. 让我们将所有函数和`multicall`辅助器放在一起：

   ```cpp
       auto call_fgh (multicall(f, g, h, nl));
   ```
7. 这里我们提供一组数字，之后这些数字就会被相应的括号包围，然后打印出来。这样，我们现在调用一次，就等于以前调用五次主函数中定义的函数。

   ```cpp
       for_each(call_fgh, 1, 2, 3, 4, 5);
   }
   ```
8. 编译运行，我们应该能得到期望的结果：

   ```cpp
   $ ./multicaller
   (1), [1], {1},
   (2), [2], {2},
   (3), [3], {3},
   (4), [4], {4},
   (5), [5], {5},
   ```

## How it works...

我们刚刚实现的辅助函数还是挺复杂的。我们使用了`std::initializer_list`来帮助我们展开参数包。为什么这里不用特殊的数据结构呢？再来看一下`for_each`的实现：

```cpp
auto for_each ([](auto f, auto ...xs) {
    (void)std::initializer_list<int>{
        ((void)f(xs), 0)...
    };
});
```

这段代码的核心在于`f(xs)`表达式。`xs`是一个参数包，我们需要将其进行解包，才能获取出独立的参数，以便调用函数f。不幸的是，我们知道这里不能简单的使用`...`标记，写成`f(xs)...`。

所以，我能做的只能是构造出一个`std::initializer_list`列表，其具有一个可变的构造函数。表达式可以直接通过`return std::initializer_list<int>{f(xs)...};`方式构建，不过其也有缺点。在让我们看一下`for_each`的实现，看起来要比之前简单许多：

```cpp
auto for_each ([](auto f, auto ...xs) {
    return std::initializer_list<int>{f(xs)...};
});
```

这看起来非常简单易懂，但是我们要了解其缺点所在：

1. 其使用f函数的所有调用返回值，构造了一个初始化列表。但我们并不关心返回值。
2. 虽然其返回的初始化列表，但是我们想要一个“即发即弃”的函数，这些函数不用返回任何东西。
3. f在这里可能是一个函数，因为其不会返回任何东西，可能在编译时就会被优化掉。

要想`for_each`修复上面所有的问题，会让其变的更加复杂。例子中做到了一下几点：

1. 不返回初始化列表，但会将所有表达式使用`(void)std::initializer_list<int>{...}`转换为`void`类型。
2. 初始化表达式中，其将`f(xs)...`包装进`(f(xs),0)...`表达式中。这会让程序将返回值完全抛弃，不过0将会放置在初始化列表中。
3. `f(xs)`在`(f(xs), 0)...`表达式中，将会再次转换成`void`，所以这里就和没有返回值一样。

这些不幸的事导致例程如此复杂丑陋，不过其能为所有可变的函数对象工作，并且不管这些函数对象是否返回值，或返回什么样的值。

这种技术可以很好控制函数调用的顺序，严格保证多个函数/函数对象以某种顺序进行调用。

> Note:
>
> 不推荐使用C风格的类型转换，因为C++有自己的转换操作。我们可以使用`reinterpret_cast<void>(expression)`代替例程中的代码行，不过这样会降低代码的可读性，会给后面的阅读者带来一些困扰。
