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

当我们有很多工作要做时，可能就会导致很多代码的重复。使用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)`代替例程中的代码行，不过这样会降低代码的可读性，会给后面的阅读者带来一些困扰。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/c-17-stl-cook-book/chapter4-0-chinese/chapter4-5-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
