### 第一部分：基本操作

#### 基本函数的构建

在这一部分中，我们将完成基本的四则运算和由它们组合而成的初等函数的构建。你需要在cc/operators中补全`ops.h`和`ops.cc`的内容。

**[TASK 1]** 在`ops.h`中，你需要补全以下函数的实现：

- `mul`函数，输入为两个数`a`、`b`，输出为它们的乘积。

- `id`函数，将输入原样输出。

- `add`函数，输入为两个数`a`、`b`，输出为它们的和。

- `neg`函数，输入为`a`，输出为`-a`。

- `lt`函数，输入为两个数`a`、`b`，输出为`(float)(a < b)`。

- `eq`函数，输入为两个数`a`、`b`，输出为`(float)(a == b)`。

- `max`函数，输入为两个数`a`、`b`，输出为`a`和`b`中较大的那个。

它们都是模板函数，相信你已经注意到了，它们都被定义在`.h`文件中，而不是`.cc`文件中，这与C++的模板的实例化机制和编译模型有关。

模板的实例化机制：模板函数或模板类并不是真正的代码，而是一个“蓝图”或“模式”，编译器在编译时根据这个蓝图生成具体的代码。这个过程称为模板实例化。例如，当你使用一个模板函数时，编译器会根据你传递的类型参数生成一个具体的函数版本。这个生成的过程发生在编译时。

编译模型：C++采用的是分离编译模型，即每个源文件（.cc 或 .cpp 文件）是独立编译的。编译器在编译一个源文件时，只会看到该源文件及其包含的头文件中的内容。如果你将模板函数的定义放在源文件中，其他源文件在编译时无法看到模板的定义，因此无法生成对应的实例化代码。

另外，你应当还注意到了我们为这两个文件提供了名叫`operators`的命名空间（namespace）。主要是为了防止不同命名空间中的重名冲突。

**[TASK 2]** 在`ops.cc`中，你需要完成以下函数的实现：

- `is_close`函数，输入为两个数`x`、`y`，输出为`(float)(abs(x - y) < epsilon)`。

- `sigmoid`函数，输入为`x`，为了方便计算，在输出时遵照下面的规则：

$$
f(x) =\left\{\begin{matrix}
\frac{1.0}{(1.0 + e^{-x})}, x\ge 0
 \\
\frac{e^x}{(1.0 + e^{x})}, \mathrm{otherwise}
\end{matrix}\right.
$$

- `relu`函数，输入为`x`，输出为`x > 0.0 ? x : 0.0`。

- `inv`函数，输入为`x`，输出为`1.0 / x`。

- `inv_back`函数，用于计算$f(x)=\frac{1}{x}$的微分$f(x)\mathrm{d}x$，输入为`x`和`d`，输出为$-\frac{d}{x^2}$。

- `relu_back`函数，输入为`x`和`d`，输出为`x > 0.0 ? d*1.0 : 0.0`。

#### 函数式编程基础

实现`map`、`zipWith`和`reduce`。

`map`接受一个`std::vector`和一个函数作为输入，返回一个新的`std::vector`，其中每个元素都是输入函数应用于输入`std::vector`中对应元素的结果。具体来说，对于下面这个实现：

```cpp
template<typename T, typename F>
auto map(const std::vector<T>& vec, F func) -> std::vector<decltype(func(std::declval<T>()))> {

    std::vector<decltype(func(std::declval<T>()))> result;
    result.reserve(vec.size());

    std::transform(vec.begin(), vec.end(), std::back_inserter(result), func);

    return result;
}
```

有几处可能让你感到疑惑的地方。

首先，这里的函数返回值居然和Python一样被后置了！`->` 是 C++11 引入的尾置返回类型语法。它的作用是将函数的返回类型放在函数参数列表之后，而不是放在函数名之前。在某些情况下，返回类型可能依赖于函数参数或模板参数，而这些信息在函数名之前是不可用的。尾置返回类型允许我们在函数参数列表之后推导返回类型。

> 例如，在`map`函数中，返回类型依赖于`func`的返回类型，而`func`的类型在函数名之前是未知的。使用尾置返回类型可以解决这个问题。

其次，我们使用了`std::declval`。`std::declval`是 C++11 引入的一个工具，用于在编译时模拟一个对象的“假实例”，以便在不实际构造对象的情况下推导类型。

```cpp
decltype(func(std::declval<T>()))
```

> 在`map`函数中，我们需要推导`func`的返回类型。假设`func`是一个函数对象，接受`T`类型的参数并返回某种类型`R`，我们可以使用`std::declval`来模拟调用`func`的过程。

**[TASK 3]** 在`ops.cc`中，调用我们给出的`map`函数实现和你刚刚完成的`neg`函数，补全`negList`函数（大约需要1行代码）。

**[TASK 4]** 在`ops.h`中，仿照`map`函数，补全`zipWith`函数（大约需要10行代码）。`zipWidth`函数接受两个`vector`和一个函数`func`作为输入，要得到一个新的`vector`，这个`vector`中的元素都是两个`vector`逐元素进行函数`func`操作之后的结果。例如，对于`vec1 = [1, 2, 3]`，`vec2 = [5, 6, 7]`，`func`为`add`，那么将返回`[6, 8, 10]`。注意：在进行`zipWith`函数的实现时，你需要考虑输入的两个`std::vector`长度不一致的情况，对于这种情况，你简单地`throw`一个异常即可。

**[TASK 5]** 在`ops.cc`中，使用你实现的`zipWith`和`add`函数，实现`addLists`函数（大约需要1行代码）。

**[TASK 6]** 实际上你会发现`std::accumulate`（问一问LLM这个是个啥）就能够承担`reduce`函数的功能，因此你可以直接使用`std::accumulate`来实现`reduce`函数。这个任务需要你使用`reduce`函数实现`sumList`（将一个列表中的元素相加）和`prodList`（将一个列表中的元素相乘）函数（大约分别需要1行代码）。

#### 检查结果

做完了？很好，切换到`cc`，执行下面的语句来编译框架

```
cmake -S . -B build
cd build
make
```
现在，编辑系统环境变量

```
echo 'export PYTHONPATH="/home/pre/uc-modern-cpp-student/cc/build"' >> ~/.bashrc
```

将??????替换为将刚刚生成的`build`文件夹的绝对目录直接粘贴到这里，这个文件夹的目录应该形如

```Python
/home/hexu/learn/uc-modern-cpp-student/cc/build
```

> 可以切换到`build`目录下，执行`pwd`命令来获取绝对路径。

好了，不出意外的话，就再也别动`~/.bashrc`了。现在还有一个`frontend/framework/basis/test_task1.py`文件。切换到目录`frontend/framework/basis/`，直接运行task1到task6的文件，如果没有任何报错，说明你已经完成了这一关！🎉