From 39515d86996369e366c7e9af80f41446f71ed30a Mon Sep 17 00:00:00 2001 From: DarkSharpness_XzY <2040703891@qq.com> Date: Sun, 24 Mar 2024 23:02:29 +0800 Subject: [PATCH] Upd(map): Update a newer version of the type_traits tutorial. --- map/tutorials/type_traits/Tutorial.md | 177 +++++++++++++++++++++----- 1 file changed, 147 insertions(+), 30 deletions(-) diff --git a/map/tutorials/type_traits/Tutorial.md b/map/tutorials/type_traits/Tutorial.md index 3536fd1..3a553c2 100644 --- a/map/tutorials/type_traits/Tutorial.md +++ b/map/tutorials/type_traits/Tutorial.md @@ -1,18 +1,21 @@ # A Little Tutorial + 这个文档对 C++ 11 中的 type_traits 做一个简短的介绍和实现指导。但主要还是需要同学们自学。 -## What is traits in C++ 11? + +## What is traits in C++ 11? + 在 C++11 中,traits 是指一组类型萃取机制(type trait mechanism),用于提取类型信息,并根据这些信息在编译期间进行类型转换和编译期间的决策。它们在头文件 `type_traits` 中。 traits 通常使用**模板元编程**的技术实现,它允许我们在编译期间确定一个类型的属性,如是否是一个指针、是否是一个常量类型、是否是一个类等等。 我们给出若干个使用 C++ 11 type_traits 的代码实例: + ```C++ #include #include template -void print_type(T x) -{ +void print_type(const T &x) { if (std::is_floating_point::value) std::cout << x << " is a floating point number.\n"; else @@ -20,8 +23,7 @@ void print_type(T x) } template -void compare_types(T x, U y) -{ +void compare_types(const T &x, const U &y) { if (std::is_same::value) std::cout << x << " and "<< y << " have the same type.\n"; else @@ -49,52 +51,58 @@ int main() { return 0; } ``` + 以及traits自定义模板类的实现示例: -``` + +```C++ // 定义数据 type 类 enum Type { - TYPE_1, - TYPE_2, - TYPE_3 + TYPE_1, + TYPE_2, + TYPE_3 } // 自定义数据类型 -class Foo { -public: - Type type = TYPE_1; +struct Foo { + static constexpr Type type = TYPE_1; }; -class Bar { -public: - Type type = TYPE_2; +struct Bar { + static constexpr Type type = TYPE_2; }; // 定义type traits的模板类,所有像这个模板类一样含有Type的类会被萃取出来进行模板化处理 template struct type_traits { - Type type = T::type; + static constexpr Type type = T::type; } // 内置数据类型同样可以进行这种萃取 template struct type_traits { - Type type = Type::TYPE_1; + static constexpr Type type = Type::TYPE_1; } template struct type_traits { - Type type = Type::TYPE_3; + static constexpr Type type = Type::TYPE_2; } + // 萃取之后进行统一处理,写统一的编码函数 template -void decode::type == Type::TYPE_1) { - ... - } - else if(type_traits::type == Type::TYPE_2) { - ... - } +void decode(const T& data, char* buf) { + if (type_traits::type == Type::TYPE_1) { + // ... + } + else if (type_traits::type == Type::TYPE_2) { + // ... + } } ``` + +当然,不仅可以用 `enum + static constexpr`,我们也可以用 `using/typedef` 来定义类型别名,然后通过 `std::is_same` 来判断类型是否相同,来实现类似的功能。 + ## Bonus + 我们的 Bonus 可以是实现类似于 C++ 11 中的 type_traits 的某些功能,但是不要求完全一致。 这里举一个示例,我们以“迭代器可否被赋值特性”给出一个简单的实现思路: + ```C++ //1.定义一个 type_traits 模板类 template @@ -109,16 +117,125 @@ struct my_false_type{ //todo }; //3.分别在可被赋值的迭代器和不可被赋值的迭代器中定义 iterator_assignable 类型 -class iterator{ +struct iterator{ using iterator_assignable = my_true_type; }; -class const_iterator{ +struct const_iterator{ 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 +#include +#include + +namespace sjtu { + +template +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 +static constexpr bool has_value_type = requires() { + typename _Tp::value_type; +}; + +template +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 +#include +#include +#include + +namespace sjtu { + +template +void print(const _Tp &__val) { + // Your code here. +} + +} // namespace sjtu + + +void sample() { + std::vector 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] +] +*/ + +```