Upd(map): Update a newer version of the type_traits tutorial.

This commit is contained in:
DarkSharpness_XzY
2024-03-24 23:02:29 +08:00
parent b48b580e1c
commit 39515d8699

View File

@ -1,18 +1,21 @@
# A Little Tutorial # A Little Tutorial
这个文档对 C++ 11 中的 type_traits 做一个简短的介绍和实现指导。但主要还是需要同学们自学。 这个文档对 C++ 11 中的 type_traits 做一个简短的介绍和实现指导。但主要还是需要同学们自学。
## What is traits in C++ 11
## What is traits in C++ 11?
在 C++11 中traits 是指一组类型萃取机制type trait mechanism用于提取类型信息并根据这些信息在编译期间进行类型转换和编译期间的决策。它们在头文件 `type_traits` 中。 在 C++11 中traits 是指一组类型萃取机制type trait mechanism用于提取类型信息并根据这些信息在编译期间进行类型转换和编译期间的决策。它们在头文件 `type_traits` 中。
traits 通常使用**模板元编程**的技术实现,它允许我们在编译期间确定一个类型的属性,如是否是一个指针、是否是一个常量类型、是否是一个类等等。 traits 通常使用**模板元编程**的技术实现,它允许我们在编译期间确定一个类型的属性,如是否是一个指针、是否是一个常量类型、是否是一个类等等。
我们给出若干个使用 C++ 11 type_traits 的代码实例: 我们给出若干个使用 C++ 11 type_traits 的代码实例:
```C++ ```C++
#include <iostream> #include <iostream>
#include <type_traits> #include <type_traits>
template<typename T> template<typename T>
void print_type(T x) void print_type(const T &x) {
{
if (std::is_floating_point<T>::value) if (std::is_floating_point<T>::value)
std::cout << x << " is a floating point number.\n"; std::cout << x << " is a floating point number.\n";
else else
@ -20,8 +23,7 @@ void print_type(T x)
} }
template<typename T, typename U> template<typename T, typename U>
void compare_types(T x, U y) void compare_types(const T &x, const U &y) {
{
if (std::is_same<T, U>::value) if (std::is_same<T, U>::value)
std::cout << x << " and "<< y << " have the same type.\n"; std::cout << x << " and "<< y << " have the same type.\n";
else else
@ -49,8 +51,10 @@ int main() {
return 0; return 0;
} }
``` ```
以及traits自定义模板类的实现示例 以及traits自定义模板类的实现示例
```
```C++
// 定义数据 type 类 // 定义数据 type 类
enum Type { enum Type {
TYPE_1, TYPE_1,
@ -58,43 +62,47 @@ enum Type {
TYPE_3 TYPE_3
} }
// 自定义数据类型 // 自定义数据类型
class Foo { struct Foo {
public: static constexpr Type type = TYPE_1;
Type type = TYPE_1;
}; };
class Bar { struct Bar {
public: static constexpr Type type = TYPE_2;
Type type = TYPE_2;
}; };
// 定义type traits的模板类所有像这个模板类一样含有Type的类会被萃取出来进行模板化处理 // 定义type traits的模板类所有像这个模板类一样含有Type的类会被萃取出来进行模板化处理
template<typename T> template<typename T>
struct type_traits { struct type_traits {
Type type = T::type; static constexpr Type type = T::type;
} }
// 内置数据类型同样可以进行这种萃取 // 内置数据类型同样可以进行这种萃取
template<typename int> template<typename int>
struct type_traits { struct type_traits {
Type type = Type::TYPE_1; static constexpr Type type = Type::TYPE_1;
} }
template<typename double> template<typename double>
struct type_traits { struct type_traits {
Type type = Type::TYPE_3; static constexpr Type type = Type::TYPE_2;
} }
// 萃取之后进行统一处理,写统一的编码函数 // 萃取之后进行统一处理,写统一的编码函数
template<typename T> template<typename T>
void decode<const T& data, char* buf) { void decode(const T& data, char* buf) {
if(type_traits<T>::type == Type::TYPE_1) { if (type_traits<T>::type == Type::TYPE_1) {
... // ...
} }
else if(type_traits<T>::type == Type::TYPE_2) { else if (type_traits<T>::type == Type::TYPE_2) {
... // ...
} }
} }
``` ```
当然,不仅可以用 `enum + static constexpr`,我们也可以用 `using/typedef` 来定义类型别名,然后通过 `std::is_same` 来判断类型是否相同,来实现类似的功能。
## Bonus ## Bonus
我们的 Bonus 可以是实现类似于 C++ 11 中的 type_traits 的某些功能,但是不要求完全一致。 我们的 Bonus 可以是实现类似于 C++ 11 中的 type_traits 的某些功能,但是不要求完全一致。
这里举一个示例,我们以“迭代器可否被赋值特性”给出一个简单的实现思路: 这里举一个示例,我们以“迭代器可否被赋值特性”给出一个简单的实现思路:
```C++ ```C++
//1.定义一个 type_traits 模板类 //1.定义一个 type_traits 模板类
template<typename T> template<typename T>
@ -109,16 +117,125 @@ struct my_false_type{
//todo //todo
}; };
//3.分别在可被赋值的迭代器和不可被赋值的迭代器中定义 iterator_assignable 类型 //3.分别在可被赋值的迭代器和不可被赋值的迭代器中定义 iterator_assignable 类型
class iterator{ struct iterator{
using iterator_assignable = my_true_type; using iterator_assignable = my_true_type;
}; };
class const_iterator{ struct const_iterator{
using iterator_assignable = my_false_type; using iterator_assignable = my_false_type;
}; };
``` ```
这部分我们不会提供提示数据,所以你需要自己设计测试代码,并且在 code review 时向助教展示。
如果想了解更多关于type traits可以参考 https://en.cppreference.com/w/cpp/header/type_traits或许其中的一些做法会给你一些思路的启发。 Tips:
我们鼓励大家多去尝试,但不建议大家在这个作业中花费太多时间 - 你可以借助 `std::true_type` 和 `std::false_type` 来实现类似功能
- 如果 `true/false` 不能满足你的需求,可以试试看模板类 `std::integral_constant` 。
### Subtask 1
在 `C++` 中,`sort` 函数包含在 `algorithm` 头文件中,要求传入的是一对随机访问迭代器。
如果用户传入了错误的迭代器,会导致编译错误。然而,`gcc` 的默认报错信息实在是难以阅读。同时,对于我们的 `map` 类型,其天然就是按照 `key` 进行排序的。因此在我们的 `sort` 中,我们希望能够特判 `map` 类型迭代器,直接返回;对于随机访问迭代器,我们使用 `std::sort`;而对于其他类型的迭代器,我们希望能够给出更加友好的报错信息。
> 如果传入了错误的 `list` 迭代器,报错信息可能如下 (没截完, 大约 100+ 行):
![看看这甜蜜的报错](https://s2.loli.net/2023/07/07/eGMABEJKSi7ahZT.png)
请修改你的 `iterator` 类型,并且完成以下的 `sort` 函数:
Tips:
- 为了避免错误的分支被实例化, 你可能需要用到 `if constexpr` 语句
- 在 C++ 14 及以后, 可以使用 `is_xxx_v` 代替 `is_xxx<>::value`
```C++
#include <iterator>
#include <algorithm>
#include <type_traits>
namespace sjtu {
template <typename _Iter>
void my_sort(_Iter __first, _Iter __last) {
// Your code here.
// For random access iterator, use std::sort.
// For sjtu::map iterator, return directly.
// For others, static_assert(false, "Not a random access iterator.");
// You may use std::iterator_traits to get the iterator category.
}
} // namespace sjtu
```
### Subtask 2
在 C++ 中,检测一个类是否存在一个成员函数等等是一个非常麻烦的事情。
在 C++ 20 之前,传统的做法是 [SFINAE](https://en.cppreference.com/w/cpp/language/sfinae) 。然而,这东西写起来非常的麻烦,这里不推荐大家去学习各种抽象的“奇技淫巧”,只需了解大致意图即可。
幸运的是,在 C++ 20 及以后,我们可以用 `requires` 来轻松的检测,其会返回一个编译期 `bool` 常数。
```C++
template <typename _Tp>
static constexpr bool has_value_type = requires() {
typename _Tp::value_type;
};
template <typename _Tp>
static constexpr bool has_member_dark = requires(const _Tp &x) {
x.dark;
};
```
其可用于辅助 `type_traits` 的编写,完成类型检测。
现在,我们要实现一个递归输出的函数 `print` ,其可以接受任意一个能用 `std::cout` 输出的元素,或者是一个容器 (比如 `std::vector` ) 且容器的元素也能用 `print` 输出。
对于元素,我们直接输出;对于容器,如果容器里面是元素,我们输出元素,元素间用空格隔开,并且在最后输出一个换行符,前后用 `[]` 包裹;如果容器里面是容器,我们直接先输出单个 `[` 并换行,然后每行递归地输出每个子容器,最后输出一个 `]` 并换行。
当然,以上要求不是必须的,你可以自由发挥,只要好看即可。
保证输入合法,且容器有 `begin()` 和 `end()` ,或者是数组。
Tips:
- 你可以试试 `std::begin` 和 `std::end` 来获取容器的迭代器。
- 感兴趣的同学可以自行去了解一下 `concept`, 可能对你会有帮助。
- 如果使用 `requires`, 记得编译选项加上 `-std=c++20`
```C++
#include <iterator>
#include <algorithm>
#include <type_traits>
#include <vector>
namespace sjtu {
template <typename _Tp>
void print(const _Tp &__val) {
// Your code here.
}
} // namespace sjtu
void sample() {
std::vector<int> a[5];
for (int i = 0 ; i < 5 ; ++i)
a[i].push_back(i), a[i].push_back(i);
sjtu::print(a);
}
/*
应当输出类似如下(总之好看即可):
[
[0 0]
[1 1]
[2 2]
[3 3]
[4 4]
]
*/
```