From 09732fe44a1589d44d675751e7297fcc530949b1 Mon Sep 17 00:00:00 2001 From: downdemo Date: Wed, 13 Mar 2019 14:08:22 +0800 Subject: [PATCH] init --- .gitignore | 1 + README.md | 20 +- docs/01_function_template.md | 542 ++++++ docs/02_class_template.md | 1072 +++++++++++ docs/03_non_type_template_parameter.md | 331 ++++ docs/04_variadic_template.md | 654 +++++++ ...5_move_semantics_and_perfect_forwarding.md | 346 ++++ docs/06_name_lookup.md | 617 ++++++ docs/07_instantiation.md | 497 +++++ docs/08_template_argument_deduction.md | 732 +++++++ docs/09_specialization_and_overloading.md | 413 ++++ docs/10_traits.md | 679 +++++++ docs/11_inheritance.md | 505 +++++ docs/12_type_erasure.md | 159 ++ docs/13_metaprogramming.md | 1693 +++++++++++++++++ docs/14_expression_template.md | 318 ++++ docs/15_debugging.md | 493 +++++ docs/_config.yml | 1 + docs/index.md | 19 + src/apply.cpp | 66 + src/dag_graph.cpp | 475 +++++ src/expression_template.cpp | 307 +++ src/function.cpp | 153 ++ src/get_class.cpp | 37 + src/get_variant_value.cpp | 55 + src/integral_constant.cpp | 79 + src/is_among.cpp | 21 + src/kv_string.cpp | 63 + src/ratio.cpp | 62 + src/tuple.cpp | 466 +++++ src/typelist.hpp | 444 +++++ src/variant.cpp | 376 ++++ 32 files changed, 11695 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 docs/01_function_template.md create mode 100644 docs/02_class_template.md create mode 100644 docs/03_non_type_template_parameter.md create mode 100644 docs/04_variadic_template.md create mode 100644 docs/05_move_semantics_and_perfect_forwarding.md create mode 100644 docs/06_name_lookup.md create mode 100644 docs/07_instantiation.md create mode 100644 docs/08_template_argument_deduction.md create mode 100644 docs/09_specialization_and_overloading.md create mode 100644 docs/10_traits.md create mode 100644 docs/11_inheritance.md create mode 100644 docs/12_type_erasure.md create mode 100644 docs/13_metaprogramming.md create mode 100644 docs/14_expression_template.md create mode 100644 docs/15_debugging.md create mode 100644 docs/_config.yml create mode 100644 docs/index.md create mode 100644 src/apply.cpp create mode 100644 src/dag_graph.cpp create mode 100644 src/expression_template.cpp create mode 100644 src/function.cpp create mode 100644 src/get_class.cpp create mode 100644 src/get_variant_value.cpp create mode 100644 src/integral_constant.cpp create mode 100644 src/is_among.cpp create mode 100644 src/kv_string.cpp create mode 100644 src/ratio.cpp create mode 100644 src/tuple.cpp create mode 100644 src/typelist.hpp create mode 100644 src/variant.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..600d2d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 045f45e..93e6523 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ -# CPP-Templates-2nd \ No newline at end of file +* C++ [模板](https://en.cppreference.com/w/cpp/language/templates)技术是泛型编程的核心,但囿于编译器技术限制,不得不带着缺陷诞生,语法晦涩,报错冗长,难以调试,应用层开发较少使用,相关技术书籍匮乏,因此掌握难度较大。模板相关的经典技术书籍主要有三本,分别是 2001 年出版的 [*Modern C++ Design*](https://book.douban.com/subject/1755195/)、2002 年出版的 [*C++ Templates*](https://book.douban.com/subject/1455780/)、2004 年出版的 [*C++ Template Metaprogramming*](https://book.douban.com/subject/1920800/)。三者基于的 C++ 标准都是 C++98,*Modern C++ Design* 涉及 [Andrei Alexandrescu](https://en.wikipedia.org/wiki/Andrei_Alexandrescu) 写书时配套的 [Loki](http://loki-lib.sourceforge.net/),*C++ Template Metaprogramming* 涉及 [Boost](https://www.boost.org/),二者以介绍元编程(模板技术的一种应用)为主,只有 *C++ Templates* 主要介绍 C++98 标准的模板技术。时过境迁,C++ 标准的更新逐步修复了一些语法缺陷,减少了使用者的心智负担,并引入了语法糖和工具,让编写模板越来越简单。2017 年 9 月 25 日,基于 C++17 标准,[*C++ Templates 2ed*](https://book.douban.com/subject/11939436/) 出版,填补了十多年间模板技术进化时相关书籍的空白,堪称最全面的模板教程,也是对 C++11/14/17 特性介绍最为全面的书籍之一。个人完整学习[原书](https://www.safaribooksonline.com/library/view/c-templates-the/9780134778808/)后,梳理精简章节脉络,补充 C++20 相关特性,如 [concepts](https://en.cppreference.com/w/cpp/concepts)、支持模板参数的 [lambda](https://en.cppreference.com/w/cpp/language/lambda) 等,运行验证所有代码结果,最终记录至此。 + +## Contents + +1. [Function template](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/01_function_template.md) +2. [Class template](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/02_class_template.md) +3. [Non-type template parameter](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/03_non_type_template_parameter.md) +4. [Variadic template](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/04_variadic_template.md) +5. [Move semantics and perfect forwarding](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/05_move_semantics_and_perfect_forwarding.md) +6. [Name lookup](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/06_name_lookup.md) +7. [Instantiation](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/07_instantiation.md) +8. [Template argument deduction](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/08_template_argument_deduction.md) +9. [Specialization and overloading](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/09_specialization_and_overloading.md) +10. [Traits](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/10_traits.md) +11. [Inheritance](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/11_inheritance.md) +12. [Type erasure](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/12_type_erasure.md) +13. [Metaprogramming](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/13_metaprogramming.md) +14. [Expression template](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/14_expression_template.md) +15. [Debugging](https://github.com/downdemo/Cpp-Templates-2ed/blob/master/docs/15_debugging.md) diff --git a/docs/01_function_template.md b/docs/01_function_template.md new file mode 100644 index 0000000..ebab189 --- /dev/null +++ b/docs/01_function_template.md @@ -0,0 +1,542 @@ +## [函数模板](https://en.cppreference.com/w/cpp/language/function_template) + +* 编写时不指定具体类型,直到使用时才能确定,这个概念就是泛型。模板,顾名思义,编写一次即可适用于任意类型。模板定义以关键词 template 开始,后跟一个模板参数列表,类型参数前必须使用关键字 typename 或 class,在模板参数列表中这两个关键字含义相同,可以互换使用。函数模板通常不用声明为 inline,唯一例外的是特定类型的全特化,因为编译器可能忽略 inline,函数模板是否内联取决于编译器的优化策略 + +```cpp +#include +#include + +namespace jc { + +template +T max(const T& a, const T& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { + assert(jc::max(1, 3) == 3); + assert(jc::max(1.0, 3.14) == 3.14); + std::string s1 = "down"; + std::string s2 = "demo"; + assert(jc::max(s1, s2) == "down"); +} +``` + +## 两阶段编译(Two-Phase Translation) + +* 模板编译分为实例化前检查和实例化两个阶段。实例化前检查模板代码本身,包括 + * 检查语法是否正确,如是否遗漏分号 + * 检查是否使用不依赖于模板参数的未知名称,如未声明的类型名、函数名 + * 检查不依赖于模板参数的静态断言 + +```cpp +template +void f(T x) { + undeclared(); // 一阶段编译错误,未声明的函数 + static_assert(sizeof(int) > 10); // 一阶段,sizeof(int) <= 10,总会编译失败 +} + +int main() {} +``` + +* 实例化期间保证代码有效,比如对不能解引用的类型进行解引用就会实例化出错,此外会再次检查依赖于模板参数的部分 + +```cpp +template +void f(T x) { + undeclared(x); // 调用 undeclared(T) 才会出现函数未声明的实例化错误 + static_assert(sizeof(T) > 10); // 如果 sizeof(T) <= 10 则实例化错误 +} + +int main() { + f(42); // 调用函数才会进行实例化,不调用则不会有实例化错误 +} +``` + +## [模板实参推断(Template Argument Deduction)](https://en.cppreference.com/w/cpp/language/template_argument_deduction) + +* 调用模板时,如果不显式指定模板参数类型,则编译器会根据传入的实参推断模板参数类型 + +```cpp +#include +#include + +namespace jc { + +template +T max(const T& a, const T& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { + assert(jc::max(1, 3) == 3); // T 推断为 int + assert(jc::max(1.0, 3.14) == 3.14); // T 推断为 double + std::string s1 = "down"; + std::string s2 = "demo"; + assert(jc::max(s1, s2) == "down"); // T 推断为 std::string +} +``` + +* 实参的推断要求一致,其本身不会为了编译通过自动做类型转换 + +```cpp +#include + +namespace jc { + +template +T max(const T& a, const T& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { + jc::max(1, 3.14); // 错误,T 分别推断出 int 和 double,类型不明确 +} +``` + +* 字符串字面值传引用会推断为字符数组(传值则推断为 `const char*`,数组和函数会 decay 为指针) + +```cpp +#include +#include + +namespace jc { + +template +T max(const T& a, const U& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { + std::string s = "down"; + jc::max("down", s); // 错误,T 推断为 char[5] 和 std::string +} +``` + +* 对于推断不一致的情况,可以显式指定类型而不使用推断机制,或者强制转换实参为希望的类型使得推断结果一致 + +```cpp +#include +#include + +namespace jc { + +template +T max(const T& a, const U& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { + std::string s = "demo"; + assert(jc::max("down", "demo") == "down"); + assert(jc::max(std::string{"down"}, s) == "down"); +} +``` + +* 也可以增加一个模板参数,这样每个实参的推断都是独立的,不会出现矛盾 + +```cpp +#include + +namespace jc { + +template +T max(const T& a, const U& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { + assert(jc::max(1, 3.14) == 3); // T 推断为 int,返回值截断为 int + assert(jc::max(1, 3.14) == 3.14); +} +``` + +* 模板实参不能推断返回类型,必须显式指定 + +```cpp +#include + +namespace jc { + +template +RT max(const T& a, const U& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { + assert(jc::max(1, 3.14) == 3.14); + assert((jc::max(1, 3.14) == 3)); +} +``` + +* C++14 允许 auto 作为返回类型,它通过 return 语句推断返回类型,C++11 则需要额外指定尾置返回类型,对于三目运算符,其结果类型为两个操作数类型中更公用的类型,比如 int 和 double 的公用类型是 double + +```cpp +#include + +namespace jc { + +template +auto max(const T& a, const U& b) -> decltype(true ? a : b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { assert(jc::max(1, 3.14) == 3.14); } +``` + +* 用 constexpr 函数可以生成编译期值 + +```cpp +namespace jc { + +template +constexpr auto max(const T& a, const U& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { static_assert(jc::max(1, 3.14) == 3.14); } +``` + +## [type traits](https://en.cppreference.com/w/cpp/header/type_traits) + +* 对于类型进行计算的模板称为 type traits,也可以称为元函数,比如用 [std::common_type](https://en.cppreference.com/w/cpp/types/common_type) 来计算不同类型中最通用的类型 + +```cpp +#include +#include + +namespace jc { + +template > +RT max(const T& a, const U& b) { + return a < b ? b : a; +} + +} // namespace jc + +int main() { assert(jc::max(1, 3.14) == 3.14); } +``` + +## 重载 + +* 当类型同时匹配普通函数和模板时,优先匹配普通函数 + +```cpp +#include + +namespace jc { + +int f(int a, int b) { return 1; } + +template +int f(const T&, const U&) { + return 2; +} + +} // namespace jc + +int main() { + assert(jc::f(1, 3) == 1); + assert(jc::f(1, 3) == 2); + assert(jc::f<>(1, 3) == 2); + assert(jc::f(1, 3.14) == 2); + assert(jc::f(3.14, 1) == 2); +} +``` + +* 模板参数不同就会构成重载,如果对于给定的实参能同时匹配两个模板,重载解析会优先匹配更特殊的模板,如果同样特殊则产生二义性错误 + +```cpp +#include + +namespace jc { + +template +int f(const T&, const U&) { + return 1; +} + +template +int f(const T& a, const U& b) { + return 2; +} + +} // namespace jc + +int main() { + assert(jc::f(1, 3.14) == 1); + assert(jc::f(1, 3.14) == 2); + // jc::f(1, 3.14); // 二义性错误 +} +``` + +* C-style 字符串的重载 + +```cpp +#include +#include +#include + +namespace jc { + +template +T max(T a, T b) { + return a < b ? b : a; +} + +template +T* max(T* a, T* b) { + return *a < *b ? b : a; +} + +const char* max(const char* a, const char* b) { + return std::strcmp(a, b) < 0 ? b : a; +} + +} // namespace jc + +int main() { + int a = 1; + int b = 3; + assert(jc::max(a, b) == b); + assert(jc::max(&a, &b) == &b); + + std::string s1 = "down"; + std::string s2 = "demo"; + assert(jc::max(s1, s2) == "down"); + assert(std::strcmp(jc::max("down", "demo"), "down") == 0); +} +``` + +* 注意不能返回 C-style 字符串的引用 + +```cpp +namespace jc { + +template +const T& f(const char* s) { + return s; +} + +} // namespace jc + +int main() { + const char* s = "downdemo"; + jc::f(s); // 错误:返回临时对象的引用 +} +``` + +* 这种错误可能在添加代码的过程中引入 + +```cpp +#include + +namespace jc { + +template +const T& max(const T& a, const T& b) { + return b < a ? a : b; +} + +// 新增函数来支持 C-style 参数 +const char* max(const char* a, const char* b) { + return std::strcmp(a, b) < 0 ? b : a; +} + +template +const T& max(const T& a, const T& b, const T& c) { + return max(max(a, b), c); // max("down", "de") 返回临时对象的引用 +} + +} // namespace jc + +int main() { + const char* a = "down"; + const char* b = "de"; + const char* c = "mo"; + jc::max(a, b, c); // 错误:返回临时对象的引用 +} +``` + +* 只有在函数调用前声明的重载才会被匹配,即使后续有更优先的匹配,由于不可见也会被忽略 + +```cpp +#include + +namespace jc { + +template +int f(T) { + return 1; +} + +template +int g(T a) { + return f(a); +} + +int f(int) { return 2; } + +} // namespace jc + +int main() { assert(jc::g(0) == 1); } +``` + +## 用于原始数组与字符串字面值(string literal)的模板 + +* 字符串字面值传引用会推断为字符数组,为此需要为原始数组和字符串字面值提供特定处理的模板 + +```cpp +#include +#include + +namespace jc { + +template +constexpr bool less(const T& a, const U& b) { + return a < b; +} + +template +constexpr bool less(T (&a)[M], T (&b)[N]) { + for (std::size_t i = 0; i < M && i < N; ++i) { + if (a[i] < b[i]) { + return true; + } + if (b[i] < a[i]) { + return false; + } + } + return M < N; +} + +} // namespace jc + +static_assert(jc::less(0, 42)); +static_assert(!jc::less("down", "demo")); +static_assert(jc::less("demo", "down")); + +int main() {} +``` + +* 各种类型的数组参数对应的偏特化 + +```cpp +#include + +namespace jc { + +template +struct A; + +template +struct A { + static constexpr int value = 1; +}; + +template +struct A { + static constexpr int value = 2; +}; + +template +struct A { + static constexpr int value = 3; +}; + +template +struct A { + static constexpr int value = 4; +}; + +template +struct A { + static constexpr int value = 5; +}; + +template +constexpr void test(int a1[7], int a2[], int (&a3)[42], int (&x0)[], T1 x1, + T2& x2, T3&& x3) { + static_assert(A::value == 5); // A + static_assert(A::value == 5); // A + static_assert(A::value == 2); // A + static_assert(A::value == 4); // A + static_assert(A::value == 5); // A + static_assert(A::value == 4); // A + static_assert(A::value == 4); // A +} + +} // namespace jc + +int main() { + int a[42]; + static_assert(jc::A::value == 1); + extern int x[]; // 传引用时将变为 int(&)[] + static_assert(jc::A::value == 3); // A + jc::test(a, a, a, x, x, x, x); +} + +int x[] = {1, 2, 3}; // 定义前置声明的数组 +``` + +## 零初始化(Zero Initialization) + +* 使用模板时常希望模板类型的变量已经用默认值初始化,但内置类型无法满足要求。解决方法是显式调用内置类型的默认构造函数 + +```cpp +namespace jc { + +template +constexpr T default_value() { + T x{}; + return x; +} + +template +struct DefaultValue { + constexpr DefaultValue() : value() {} + T value; +}; + +template +struct DefaultValue2 { + T value{}; +}; + +static_assert(default_value() == false); +static_assert(default_value() == 0); +static_assert(default_value() == 0); +static_assert(default_value() == 0); + +static_assert(DefaultValue{}.value == false); +static_assert(DefaultValue{}.value == 0); +static_assert(DefaultValue{}.value == 0); +static_assert(DefaultValue{}.value == 0); + +static_assert(DefaultValue2{}.value == false); +static_assert(DefaultValue2{}.value == 0); +static_assert(DefaultValue2{}.value == 0); +static_assert(DefaultValue2{}.value == 0); + +} // namespace jc + +int main() {} +``` diff --git a/docs/02_class_template.md b/docs/02_class_template.md new file mode 100644 index 0000000..957bb8b --- /dev/null +++ b/docs/02_class_template.md @@ -0,0 +1,1072 @@ +## [类模板](https://en.cppreference.com/w/cpp/language/class_template) + +* 和函数类似,类也支持泛型,比如实现一个基于拓扑排序遍历的有向无环图的森林 + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jc { + +template +struct DAGNode { + K k; + V v; + std::set*> in; + std::set*> out; +}; + +template +class DAGGraph { + public: + bool AddEdge(const K& from, const K& to); + + V& operator[](const K& key); + + bool Exist(const K& key) const; + + void Clear(); + + std::size_t Size() const; + + void Walk(std::function f, + bool start_from_head = true); + + void WalkHeads(std::function f); + + void WalkTails(std::function f); + + std::unordered_set NextKeys(); + + std::unordered_set NextKeys(const K& key); + + private: + bool IsCyclic(const DAGNode& from, const DAGNode& to) const; + + void RefreshWalkSequences(); + + std::vector> ConnectedComponents() const; + + void DFS(const K& k, std::unordered_set* visited, + std::set* connected_components) const; + + std::vector TopologicalSequence(const std::set& connected_components, + bool start_from_head) const; + + private: + std::map> bucket_; + std::unordered_set heads_; + std::unordered_set tails_; + std::vector> sequences_start_from_head_; + std::vector> sequences_start_from_tail_; + + private: + bool allow_modify_ = true; + std::vector> sequences_start_from_head_for_next_; + std::unordered_set current_heads_for_next_; +}; + +template +inline bool DAGGraph::AddEdge(const K& from, const K& to) { + assert(allow_modify_); + if (from == to || !bucket_.count(from) || !bucket_.count(to) || + IsCyclic(bucket_.at(from), bucket_.at(to))) { + return false; + } + bucket_.at(from).out.emplace(&bucket_.at(to)); + bucket_.at(to).in.emplace(&bucket_.at(from)); + heads_.erase(to); + tails_.erase(from); + sequences_start_from_head_.clear(); + sequences_start_from_tail_.clear(); + return true; +} + +template +inline V& DAGGraph::operator[](const K& key) { + if (!bucket_.count(key)) { + assert(allow_modify_); + bucket_[key].k = key; + heads_.emplace(key); + tails_.emplace(key); + sequences_start_from_head_.clear(); + sequences_start_from_tail_.clear(); + } + return bucket_.at(key).v; +} + +template +inline bool DAGGraph::Exist(const K& key) const { + return bucket_.count(key); +} + +template +inline void DAGGraph::Clear() { + allow_modify_ = true; + bucket_.clear(); + heads_.clear(); + tails_.clear(); + sequences_start_from_head_.clear(); + sequences_start_from_tail_.clear(); +} + +template +inline std::size_t DAGGraph::Size() const { + return bucket_.size(); +} + +template +inline void DAGGraph::Walk(std::function f, + bool start_from_head) { + if (sequences_start_from_head_.empty()) { + RefreshWalkSequences(); + } + const std::vector>& seqs_to_walk = + start_from_head ? sequences_start_from_head_ : sequences_start_from_tail_; + for (const std::vector& seq : seqs_to_walk) { + std::for_each(std::begin(seq), std::end(seq), [&](const K& key) { + const DAGNode& node = bucket_.at(key); + f(node.k, node.v); + }); + } +} + +template +inline void DAGGraph::WalkHeads( + std::function f) { + if (sequences_start_from_head_.empty()) { + RefreshWalkSequences(); + } + for (const std::vector& seq : sequences_start_from_head_) { + std::for_each(std::begin(seq), std::end(seq), [&](const K& key) { + if (heads_.count(key)) { + const DAGNode& node = bucket_.at(key); + f(node.k, node.v); + } + }); + } +} + +template +inline void DAGGraph::WalkTails( + std::function f) { + if (sequences_start_from_head_.empty()) { + RefreshWalkSequences(); + } + for (const std::vector& seq : sequences_start_from_tail_) { + std::for_each(std::begin(seq), std::end(seq), [&](const K& key) { + if (tails_.count(key)) { + const DAGNode& node = bucket_.at(key); + f(node.k, node.v); + } + }); + } +} + +template +inline std::unordered_set DAGGraph::NextKeys() { + assert(allow_modify_); // allowed call once unless Clear() + allow_modify_ = false; + current_heads_for_next_ = heads_; + if (sequences_start_from_head_.empty()) { + RefreshWalkSequences(); + } + return heads_; +} + +template +inline std::unordered_set DAGGraph::NextKeys(const K& key) { + assert(!allow_modify_); // must call NextKeys() before + assert(current_heads_for_next_.count(key)); + current_heads_for_next_.erase(key); + + std::unordered_set res; + for (std::vector& seq : sequences_start_from_head_for_next_) { + auto it = std::find(begin(seq), std::end(seq), key); + if (it == std::end(seq)) { + continue; + } + seq.erase(it); + const std::set*>& nodes = bucket_.at(key).out; + for (DAGNode* v : nodes) { + const std::set*>& prev_nodes = v->in; + bool no_prev_node_in_seq = + std::all_of(std::begin(prev_nodes), std::end(prev_nodes), + [&](DAGNode* in_node) { + return std::find(std::begin(seq), std::end(seq), + in_node->k) == std::end(seq); + }); + if (no_prev_node_in_seq) { + current_heads_for_next_.emplace(v->k); + res.emplace(v->k); + } + } + break; + } + return res; +} + +template +inline bool DAGGraph::IsCyclic(const DAGNode& from, + const DAGNode& to) const { + std::queue*> q; + for (DAGNode* v : from.in) { + q.emplace(v); + } + + std::unordered_set*> visited; + while (!q.empty()) { + DAGNode* node = q.front(); + q.pop(); + if (visited.count(node)) { + continue; + } + if (node == &to) { + return true; + } + visited.emplace(node); + for (DAGNode* v : node->in) { + q.emplace(v); + } + } + + return false; +} + +template +inline void DAGGraph::RefreshWalkSequences() { + sequences_start_from_head_.clear(); + sequences_start_from_tail_.clear(); + + const std::vector> connected_components = ConnectedComponents(); + for (const std::set& x : connected_components) { + const std::vector seq_from_head = TopologicalSequence(x, true); + const std::vector seq_from_tail = TopologicalSequence(x, false); + assert(!seq_from_head.empty()); + assert(!seq_from_tail.empty()); + sequences_start_from_head_.emplace_back(seq_from_head); + sequences_start_from_tail_.emplace_back(seq_from_tail); + } + + sequences_start_from_head_for_next_ = sequences_start_from_head_; +} + +template +inline std::vector> DAGGraph::ConnectedComponents() const { + std::vector> res; + std::unordered_set visited; + for (auto& x : bucket_) { + std::set tmp; + DFS(x.second.k, &visited, &tmp); + if (!tmp.empty()) { + res.emplace_back(tmp); + } + } + std::sort(std::begin(res), std::end(res), + [&](const std::set& lhs, const std::set& rhs) { + return lhs.size() < rhs.size(); + }); + return res; +} + +template +inline void DAGGraph::DFS(const K& k, std::unordered_set* visited, + std::set* connected_components) const { + if (visited->count(k)) { + return; + } + visited->emplace(k); + connected_components->emplace(k); + if (!bucket_.at(k).in.empty()) { + for (DAGNode* v : bucket_.at(k).in) { + DFS(v->k, visited, connected_components); + } + } + if (!bucket_.at(k).out.empty()) { + for (DAGNode* v : bucket_.at(k).out) { + DFS(v->k, visited, connected_components); + } + } +} + +template +inline std::vector DAGGraph::TopologicalSequence( + const std::set& connected_components, bool start_from_head) const { + std::map> adjacency_list; + std::map in_degree; + + for (const K& key : connected_components) { + if (!in_degree.count(key)) { + in_degree.emplace(key, 0); + } + const std::set*>& nodes = + start_from_head ? bucket_.at(key).out : bucket_.at(key).in; + for (DAGNode* v : nodes) { + adjacency_list[key].emplace_back(v->k); + ++in_degree[v->k]; + } + } + + std::queue q; + for (auto& x : in_degree) { + if (x.second == 0) { + q.emplace(x.first); + } + } + + std::vector res; + while (!q.empty()) { + const K key = q.front(); + q.pop(); + res.emplace_back(key); + for (const K& k : adjacency_list[key]) { + if (--in_degree.at(k) == 0) { + q.emplace(k); + } + } + } + + assert(res.size() == connected_components.size()); // graph is DAG + return res; +} + +} // namespace jc + +namespace jc::test { + +class MockPipelineEngine { + public: + void Start() {} + void Stop() {} + void Destroy() {} +}; + +void test() { + DAGGraph> d; + // Make Direct Acyclic Graph: + // 0 6 11 13 + // / \ | | + // 1 3 7 8 12 + // | x | \ / + // 2 4 9 + // \ / | + // 5 10 + // Traverse each child graph in order whose size smaller + + // Start Order: + // 13 + // 6 -> 7 + // 8 -> 11 -> 12 -> 9 -> 10 + // 0 -> 1 -> 3 -> 2 -> 4 -> 5 + // Stop Order: + // 13 + // 7 -> 6 + // 10 -> 9 -> 8 -> 12 -> 11 + // 5 -> 2 -> 4 -> 1 -> 3 -> 0 + + constexpr int nodes_count = 14; + for (int i = 0; i < nodes_count; ++i) { + d[i].reset(new MockPipelineEngine); + } + assert(d.AddEdge(0, 1)); + assert(d.AddEdge(0, 3)); + assert(d.AddEdge(1, 2)); + assert(d.AddEdge(3, 4)); + assert(d.AddEdge(1, 4)); + assert(d.AddEdge(3, 2)); + assert(d.AddEdge(2, 5)); + assert(d.AddEdge(4, 5)); + assert(d.AddEdge(6, 7)); + assert(d.AddEdge(8, 9)); + assert(d.AddEdge(9, 10)); + assert(d.AddEdge(11, 12)); + assert(d.AddEdge(12, 9)); + + assert(d.Size() == nodes_count); + + for (int i = 0; i < nodes_count; ++i) { + assert(d.Exist(i)); + } + + assert(!d.AddEdge(1, 0)); + assert(!d.AddEdge(2, 0)); + assert(!d.AddEdge(4, 0)); + assert(!d.AddEdge(7, 6)); + assert(!d.AddEdge(10, 11)); + assert(!d.AddEdge(13, 13)); + assert(!d.AddEdge(13, 14)); + + constexpr bool start_from_head = true; + { + std::vector v; + std::vector start_order{13, 6, 7, 8, 11, 12, 9, 10, 0, 1, 3, 2, 4, 5}; + d.Walk( + [&](int key, const std::unique_ptr& pipeline) { + pipeline->Start(); + v.emplace_back(key); + }, + start_from_head); + assert(v == start_order); + } + + { + std::vector v; + std::vector stop_order{13, 7, 6, 10, 9, 8, 12, 11, 5, 2, 4, 1, 3, 0}; + d.Walk( + [&](int key, const std::unique_ptr& pipeline) { + pipeline->Stop(); + v.emplace_back(key); + }, + !start_from_head); + assert(v == stop_order); + } + + { + std::vector v; + std::vector heads_order{13, 6, 8, 11, 0}; + d.WalkHeads( + [&](int key, const std::unique_ptr& pipeline) { + pipeline->Destroy(); + v.emplace_back(key); + }); + assert(v == heads_order); + } + + { + std::vector v; + std::vector tails_order{13, 7, 10, 5}; + d.WalkTails( + [&](int key, const std::unique_ptr& pipeline) { + pipeline->Destroy(); + v.emplace_back(key); + }); + assert(v == tails_order); + } + + { + std::vector test_sequence{13, 6, 7, 0, 1, 3, 4, + 2, 5, 8, 11, 12, 9, 10}; + + std::unordered_set heads{0, 6, 8, 11, 13}; + assert(d.NextKeys() == heads); + + std::vector> next_keys{ + {}, {7}, {}, {1, 3}, {}, {2, 4}, {}, {5}, {}, {}, {12}, {9}, {10}, {}, + }; + + assert(test_sequence.size() == nodes_count); + assert(next_keys.size() == nodes_count); + for (int i = 0; i < nodes_count; ++i) { + assert(d.NextKeys(test_sequence[i]) == next_keys[i]); + } + } + + d.Clear(); + assert(d.Size() == 0); + for (int i = 0; i < nodes_count; ++i) { + assert(!d.Exist(i)); + } +} + +} // namespace jc::test + +int main() { jc::test::test(); } +``` + +* 对于不同类型模板参数的类模板,会为每个类型实例化出不同的类,类的函数被调用时才实例化,类模板的 static 数据成员会分别在每个不同的类中实例化,static 数据成员和成员函数只被同一个类共享。通过 [C++ Insights](https://github.com/andreasfertig/cppinsights) 可以查看类的实例化代码,该工具提供了[在线版本](https://cppinsights.io/) + +```cpp +namespace jc { + +template +class A { + public: + static int value(); + + private: + static int n; +}; + +template +inline int A::value() { + return n; +} + +template +int A::n = 0; + +} // namespace jc + +int main() { + using namespace jc; + A a; // 实例化 A::n + A b, c; // 实例化 A::n,bc 共享 A::value() 和 A::n + int n = A::value(); // 实例化 A::value() + n = b.value(); // 使用 A::value() + n = A::value(); // 必须指定模板参数以确定实例化版本 +} +``` + +* 由于函数被调用时才实例化,如果不调用实例化时会出错的函数,代码也能通过编译 + +```cpp +namespace jc { + +template +class A { + public: + static int value(); + + private: + static int n; +}; + +template +inline int A::value() { + return f(n); +} + +template +int A::n = 0; + +} // namespace jc + +int main() { + using namespace jc; + A a; // OK,实例化 A::n + // a.value(); // 实例化错误,f 未声明 + // A::value(); // 实例化错误,f 未声明 +} +``` + +## 友元 + +* 类内定义友元可以省略模板参数,但友元函数在类模板实例化后才会实例化,如果类模板中的友元函数不包含模板参数,则会出现重定义的错误 + +```cpp +#include +#include + +namespace jc { + +template +class A { + // 类作用域内的 A 是注入类名,等价于 A + friend std::ostream& operator<<(std::ostream& os, const A& rhs) { + return os << "A<" << typeid(T).name() << "> = " << rhs.n; + } + + friend void f() {} + + private: + int n = 0; +}; + +} // namespace jc + +int main() { + jc::A a; // 实例化 operator<<(std::ostream&, const A&) 和 f() + std::cout << a; // A = 0 + // jc::A b; // 错误:第二次实例化 f(),重定义 +} +``` + +* 类外定义友元不会有重定义的问题,需要在类内声明处为类模板额外指定不同的模板参数 + +```cpp +#include +#include + +namespace jc { + +template +class A { + template + friend std::ostream& operator<<(std::ostream& os, const A& rhs); + + friend void f(); + + private: + int n = 0; +}; + +template +std::ostream& operator<<(std::ostream& os, const A& rhs) { + return os << "A<" << typeid(T).name() << "> = " << rhs.n; +} + +void f() {} + +} // namespace jc + +int main() { + jc::A a; + std::cout << a; // A = 0 + jc::A b; + std::cout << b; // A = 0 +} +``` + +* 如果要在类外定义友元,又不想在类内声明额外指定模板参数,则可以将友元声明为函数模板,在类内使用模板实例作为友元 + +```cpp +#include +#include + +namespace jc { + +template +class A; + +template +std::ostream& operator<<(std::ostream& os, const A& rhs); + +template +class A { + friend std::ostream& operator<<(std::ostream& os, const A& rhs); + + private: + int n = 0; +}; + +template +std::ostream& operator<<(std::ostream& os, const A& rhs) { + return os << "A<" << typeid(T).name() << "> = " << rhs.n; +} + +} // namespace jc + +int main() { + jc::A a; + std::cout << a; // A = 0 +} +``` + +* 如果将类模板实例声明为友元,则类模板必须已声明并可见 + +```cpp +namespace jc { + +template +struct Node; + +template +struct Tree { + friend class Node; // 友元类模板必须已声明并可见 + friend class A; // 友元类可以未声明 +}; + +} // namespace jc + +int main() {} +``` + +* 模板参数可以是友元 + +```cpp +namespace jc { + +template +class A { + friend T; // 如果 T 不是 class 则忽略 + + private: + int n = 0; +}; + +class B { + public: + static int f(const A& a) { return a.n; } +}; + +} // namespace jc + +int main() {} +``` + +## [特化(Specialization)](https://en.cppreference.com/w/cpp/language/template_specialization) + +* 特化一般指的是全特化,即为一种特定类型指定一个特定实现,该类型将不使用模板的实例化版本 + +```cpp +#include + +namespace jc { + +template +class A { + public: + int f() { return 1; } +}; + +template <> +class A { + public: + int f() { return 2; } + int g() { return 3; } +}; + +} // namespace jc + +int main() { + jc::A a; + assert(a.f() == 1); + jc::A b; + assert(b.f() == 2); + assert(b.g() == 3); +} +``` + +## [偏特化(Partial Specialization)](https://en.cppreference.com/w/cpp/language/partial_specialization) + +* 偏特化是为一类类型指定特定实现,是一种更通用的特化 + +```cpp +#include + +namespace jc { + +template +class A { + public: + int f() { return 1; } +}; + +template +class A { + public: + int f() { return 2; } + int g() { return 3; } +}; + +} // namespace jc + +int main() { + jc::A a; + assert(a.f() == 1); + jc::A b; + assert(b.f() == 2); + assert(b.g() == 3); + jc::A*> c; + assert(c.f() == 2); + assert(c.g() == 3); +} +``` + +* 偏特化可以指定多个模板参数之间的关系,如果多个偏特化匹配程度相同,将产生二义性错误。如果模板声明是一个普通声明(没有在模板名称后添加尖括号),这个声明就是一个主模板(primary template),编写偏特化通常会有一个主模板和其他偏特化模板 + +```cpp +namespace jc { + +template +struct A; // primary template + +template +struct A { + static constexpr int i = 1; +}; + +template +struct A { + static constexpr int j = 2; +}; + +template +struct A { + static constexpr int k = 3; +}; + +} // namespace jc + +using namespace jc; + +static_assert(A::i == 1); +static_assert(A::j == 2); +static_assert(A::k == 3); + +int main() { + // A{}; // 错误,匹配 A 和 A + // A{}; // 错误,匹配 A 和 A +} +``` + +* 如果多个特化中,有一个匹配程度最高,则不会有二义性错误 + +```cpp +namespace jc { + +template +struct A; + +template +struct A { + static constexpr int i = 1; +}; + +template +struct A { + static constexpr int j = 2; +}; + +template +struct A { + static constexpr int k = 3; +}; + +template +struct A { + static constexpr int k = 4; +}; + +} // namespace jc + +static_assert(jc::A::i == 1); +static_assert(jc::A::j == 2); +static_assert(jc::A::k == 3); +static_assert(jc::A::k == 3); +static_assert(jc::A::k == 4); +static_assert(jc::A::k == 4); + +int main() {} +``` + +* 偏特化常用于元编程 + +```cpp +#include +#include + +namespace jc { + +template +struct is_among; + +template class Tuple, typename... List> +struct is_among> + : std::disjunction...> {}; + +template +inline constexpr bool is_among_v = is_among::value; + +} // namespace jc + +static_assert(jc::is_among_v>); +static_assert(!jc::is_among_v>); + +int main() {} +``` + +* 偏特化遍历 [std::tuple](https://en.cppreference.com/w/cpp/utility/tuple) + +```cpp +#include +#include +#include + +namespace jc { + +template +struct PrintImpl { + static void impl(const std::tuple& t) { + std::cout << std::get(t) << " "; + PrintImpl::impl(t); + } +}; + +template +struct PrintImpl { + static void impl(const std::tuple& t) {} +}; + +template +void Print(const std::tuple& t) { + PrintImpl<0, sizeof...(List), List...>::impl(t); +} + +} // namespace jc + +int main() { + auto t = std::make_tuple(3.14, 42, "hello world"); + jc::Print(t); // 3.14 42 hello world +} +``` + +* [成员模板](https://en.cppreference.com/w/cpp/language/member_template)也能特化或偏特化 + +```cpp +#include +#include + +namespace jc { + +struct A { + template + T as() const { + return s; + } + + std::string s; +}; + +template <> +inline bool A::as() const { + return s == "true"; +} + +} // namespace jc + +int main() { + jc::A a{"hello"}; + assert(a.as() == "hello"); + assert(!a.as()); + jc::A b{"true"}; + assert(b.as()); +} +``` + +* 成员函数模板不能为虚函数,因为虚函数表的大小是固定的,而成员函数模板的实例化个数要编译完成后才能确定 + +```cpp + +namespace jc { + +template +class Dynamic { + public: + virtual ~Dynamic() {} // OK,每个 Dynamic 对应一个析构函数 + + template + virtual void f(const U&) {} // 错误,编译器不知道一个 Dynamic 中 f() 个数 +}; + +} // namespace jc + +int main() {} +``` + +## 模板的模板参数(Template Template Parameter) + +* 如果模板参数的类型是类模板,则需要使用模板的模板参数。对于模板的模板参数,C++11 之前只能用 class 关键字修饰,C++11 及其之后可以用别名模板的名称来替代,C++17 可以用 typename 修饰 + +```cpp +#include +#include + +namespace jc { + +template class Container> +void f(const Container&) {} + +} // namespace jc + +int main() { + jc::f(std::vector{}); + jc::f(std::vector{}); + jc::f(std::set{}); +} +``` + +* 实际上容器还有一个模板参数,即内存分配器 allocator + +```cpp +#include +#include +#include + +namespace jc { + +template > + class Container = std::deque> +class Stack { + public: + using reference = T&; + using const_reference = const T&; + + template class> + friend class Stack; + + template > + class Container2> + Stack& operator=(const Stack&); + + void push(const T&); + + void pop(); + + reference top(); + + const_reference top() const; + + std::size_t size() const; + + bool empty() const; + + private: + Container container_; +}; + +template class Container> +template class Container2> +inline Stack& Stack::operator=( + const Stack& rhs) { + container_.assign(rhs.container_.begin(), rhs.container_.end()); + return *this; +} + +template class Container> +inline void Stack::push(const T& x) { + container_.emplace_back(x); +} + +template class Container> +inline void Stack::pop() { + assert(!empty()); + container_.pop_back(); +} + +template class Container> +inline typename Stack::reference Stack::top() { + assert(!empty()); + return container_.back(); +} + +template class Container> +inline typename Stack::const_reference Stack::top() + const { + assert(!empty()); + return container_.back(); +} + +template class Container> +inline std::size_t Stack::size() const { + return container_.size(); +} + +template class Container> +inline bool Stack::empty() const { + return container_.empty(); +} + +} // namespace jc + +int main() { + jc::Stack s; + s.push("hello"); + s.push("world"); + assert(s.size() == 2); + assert(s.top() == "world"); + s.pop(); + assert(s.size() == 1); + assert(s.top() == "hello"); + s.pop(); + assert(s.empty()); +} +``` diff --git a/docs/03_non_type_template_parameter.md b/docs/03_non_type_template_parameter.md new file mode 100644 index 0000000..22fff07 --- /dev/null +++ b/docs/03_non_type_template_parameter.md @@ -0,0 +1,331 @@ +## [非类型模板参数(Non-type Template Parameter)](https://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter) + +* 非类型模板参数表示在编译期或链接期可以确定的常量值 + +```cpp +#include +#include + +namespace jc { + +template +std::size_t find_next(const std::bitset& b, std::size_t cur) { + for (std::size_t i = cur + 1; i < N; ++i) { + if (!(b.test(i) ^ IsSet)) { + return i; + } + } + return N; +} + +template +std::size_t find_prev(const std::bitset& b, std::size_t cur) { + if (cur > N) { + cur = N; + } + for (int i = static_cast(cur) - 1; i >= 0; --i) { + if (!(b.test(i) ^ IsSet)) { + return i; + } + } + return N; +} + +template +std::size_t circular_find_next(const std::bitset& b, std::size_t cur) { + if (cur > N) { + cur = N; + } + std::size_t res = find_next(b, cur); + if (res != N) { + return res; + } + for (std::size_t i = 0; i < cur; ++i) { + if (!(b.test(i) ^ IsSet)) { + return i; + } + } + return N; +} + +template +std::size_t circular_find_prev(const std::bitset& b, std::size_t cur) { + if constexpr (N == 0) { + return N; + } + std::size_t res = find_prev(b, cur); + if (res != N) { + return res; + } + for (std::size_t i = N - 1; i > cur; --i) { + if (!(b.test(i) ^ IsSet)) { + return i; + } + } + return N; +} + +} // namespace jc + +void test_find_next() { + std::bitset<8> b{"10010111"}; + static constexpr int _next_set[] = {1, 2, 4, 4, 7, 7, 7, 8, 8, 8}; + static constexpr int _next_unset[] = {3, 3, 3, 5, 5, 6, 8, 8, 8, 8}; + + for (std::size_t i = 0; i < std::size(_next_set); ++i) { + assert(jc::find_next(b, i) == _next_set[i]); + assert(jc::find_next(b, i) == _next_unset[i]); + } +} + +void test_find_prev() { + std::bitset<8> b{"10010110"}; + static constexpr int _prev_set[] = {8, 8, 1, 2, 2, 4, 4, 4, 7, 7}; + static constexpr int _prev_unset[] = {8, 0, 0, 0, 3, 3, 5, 6, 6, 6}; + + for (std::size_t i = 0; i < std::size(_prev_set); ++i) { + assert(jc::find_prev(b, i) == _prev_set[i]); + assert(jc::find_prev(b, i) == _prev_unset[i]); + } +} + +void test_circular_find_next() { + std::bitset<8> b{"01010111"}; + static constexpr int _next_set[] = {1, 2, 4, 4, 6, 6, 0, 0, 0, 0}; + static constexpr int _next_unset[] = {3, 3, 3, 5, 5, 7, 7, 3, 3, 3}; + + for (std::size_t i = 0; i < std::size(_next_set); ++i) { + assert(jc::circular_find_next(b, i) == _next_set[i]); + assert(jc::circular_find_next(b, i) == _next_unset[i]); + } +} + +void test_circular_find_prev() { + std::bitset<8> b{"10011001"}; + static constexpr int _prev_set[] = {7, 0, 0, 0, 3, 4, 4, 4, 7, 7}; + static constexpr int _prev_unset[] = {6, 6, 1, 2, 2, 2, 5, 6, 6, 6}; + + for (std::size_t i = 0; i < std::size(_prev_set); ++i) { + assert(jc::circular_find_prev(b, i) == _prev_set[i]); + assert(jc::circular_find_prev(b, i) == _prev_unset[i]); + } +} + +int main() { + test_find_next(); + test_find_prev(); + test_circular_find_next(); + test_circular_find_prev(); +} +``` + +* 模板参数可以由之前的参数推断类型,C++17 允许将非类型模板参数定义为 auto 或 decltype(auto) + +```cpp +#include + +namespace jc { + +template +struct get_class; + +template +struct get_class { + using type = ClassType; +}; + +template +using get_class_t = typename get_class::type; + +template +class Wrapper { + public: + Wrapper(get_class_t& obj) : obj_(obj) {} + + void increase() { ++(obj_.*ClassMember); } + + private: + get_class_t& obj_; +}; + +struct A { + int i = 0; +}; + +} // namespace jc + +int main() { + jc::A a; + jc::Wrapper<&jc::A::i>{a}.increase(); + assert(a.i == 1); +} +``` + +* C++14 允许 auto 作返回类型 + +```cpp +namespace jc { + +template +constexpr auto add(const T& a, const U& b) { + return a + b; +} + +} // namespace jc + +static_assert(jc::add('A', 2) == 'C'); + +int main() {} +``` + +## 限制 + +* C++20 之前,非类型模板参数不能是浮点数 + +```cpp +namespace jc { + +template +struct A { + static constexpr auto f() { return N; } +}; + +} // namespace jc + +static_assert(jc::A<42>::f() == 42); +static_assert(jc::A<3.14>::f() == 3.14); // C++20 + +int main() {} +``` + +* 不能用字符串字面值常量、临时对象、数据成员或其他子对象作模板实参。C++ 标准演进过程中逐渐放宽了对字符数组作为模板实参的限制,C++11 仅允许外链接(external linkage,不定义于单一的文件作用域,链接到全局符号表),C++14 允许外链接或内链接(internal linkage,只能在单个文件内部看到,不能被其他文件访问,不暴露给链接器),C++17 不要求链接 + +```cpp +namespace jc { + +template +struct A {}; + +} // namespace jc + +constexpr const char* s1 = "hello"; // 内链接对象的指针 +extern const char s2[] = "world"; // 外链接 +const char s3[] = "down"; // 内链接 + +int main() { + static const char s4[] = "demo"; // 无链接 + jc::A<"downdemo">{}; // 错误 + jc::A{}; // 错误 + jc::A{}; // C++11 允许 + jc::A{}; // C++14 允许 + jc::A{}; // C++17 允许 +} +``` + +* 非类型模板参数可以是左值引用,此时实参必须是静态常量 + +```cpp +#include + +namespace jc { + +template +struct A { + A() { ++N; } + ~A() { --N; } +}; + +void test() { + static int n = 0; + { + A a; + assert(n == 1); + } + assert(n == 0); +} + +} // namespace jc + +int main() { jc::test(); } +``` + +* 函数和数组类型作为非类型模板参数会退化为指针类型 + +```cpp +namespace jc { + +template +struct Lexer {}; + +// template +// struct Lexer {}; // 错误:重定义 + +template +struct FuncWrap {}; + +// template +// struct FuncWrap {}; // 错误:重定义 + +} // namespace jc + +int main() {} +``` + +* 如果模板实参的表达式有大于号,必须用小括号包裹表达式,否则大于号会被编译器视为表示参数列表终止的右尖括号,导致编译错误 + +```cpp +namespace jc { + +template +struct A { + inline static constexpr bool value = b; +}; + +} // namespace jc + +int main() { static_assert(jc::A<(sizeof(int) > 0)>::value); } +``` + +## [变量模板(Variable Template)](https://en.cppreference.com/w/cpp/language/variable_template) + +* C++14 提供了变量模板 + +```cpp +namespace jc { + +template +constexpr T pi{static_cast(3.1415926535897932385)}; + +static_assert(pi == true); +static_assert(pi == 3); +static_assert(pi == 3.1415926535897932385); +static_assert(pi<> == 3.1415926535897932385); + +} // namespace jc + +int main() {} +``` + +* 变量模板可以由非类型参数参数化 + +```cpp +#include +#include + +namespace jc { + +template +std::array arr{}; + +template +constexpr decltype(N) x = N; + +} // namespace jc + +static_assert(jc::x<'c'> == 'c'); + +int main() { + jc::arr<10>[0] = 42; + assert(jc::arr<10>[0] == 42); +} +``` diff --git a/docs/04_variadic_template.md b/docs/04_variadic_template.md new file mode 100644 index 0000000..901117c --- /dev/null +++ b/docs/04_variadic_template.md @@ -0,0 +1,654 @@ +## 变参模板(Variadic Template) + +* 如果函数要接受任意数量任意类型的参数,没有模板时可以通过 [std::va_list](https://en.cppreference.com/w/cpp/utility/variadic/va_list) 实现 + +```cpp +#include +#include +#include +#include +#include + +namespace jc { + +void test(int n, ...) { + std::va_list args; + va_start(args, n); + assert(va_arg(args, double) == 3.14); + assert(va_arg(args, int) == 42); + assert(std::strcmp(va_arg(args, const char*), "hello") == 0); + assert(std::strcmp(va_arg(args, const char*), "world") == 0); + va_end(args); +} + +void test(const char* fmt, ...) { + char buf[256]; + std::va_list args; + va_start(args, fmt); + std::vsnprintf(buf, 256, fmt, args); + va_end(args); + assert(std::strcmp(buf, "3.14 42 hello world") == 0); +} + +} // namespace jc + +int main() { + jc::test(4, 3.14, 42, std::string{"hello"}.c_str(), "world"); + jc::test("%.2f %d %s %s", 3.14, 42, std::string{"hello"}.c_str(), "world"); +} +``` + +* C++11 引入了变参模板,用省略号表示一个[参数包](https://en.cppreference.com/w/cpp/language/parameter_pack),类型名后接省略号表示任意数量给定类型的参数。在表达式后跟省略号,如果表达式中有参数包,就会把表达式应用到参数包中的每个参数。如果表达式中出现两次参数包,对整个表达式扩展,而不会做笛卡尔积计算 + +```cpp +#include +#include +#include +#include + +namespace jc { + +void print() {} // 参数包展开到零参数时调用 + +template +void print(const T& t, Args&&... args) { + std::cout << t << ","; + print(std::forward(args)...); +} + +template +struct A {}; + +template +void test1(const std::tuple& t, A) { + print(std::get(t)...); // print(std::get<2>(t), std::get<3>(t)); +} + +template +void test2(const std::tuple& t, A) { + print((std::get(t) + std::get(t))...); +} + +} // namespace jc + +int main() { + auto t = std::make_tuple(3.14, 42, std::string{"hello"}, "world"); + jc::test1(t, jc::A<2, 3>{}); // hello,world + jc::test2(t, jc::A<0, 1, 2>{}); // 6.28,84,hellohello, +} +``` + +* 注意参数包的省略号不能直接接在数值字面值后 + +```cpp +template +void f(const Args&... args) { + print(args + 1...); // ERROR:1... 是带多个小数点的字面值,不合法 + print(args + 1 ...); // OK + print((args + 1)...); // OK +} +``` + +* 可以直接用逗号运算符做参数包扩展,逗号左侧是对参数包每个元素做的操作,右侧是一个无关紧要的值,这样展开后对每个元素都做了操作,并形成了一个以无关值为元素的数组,这个数组无作用,只是为了满足扩展时省略号不能为表达式最后的 token 而引入 + +```cpp +#include +#include +#include + +namespace jc { + +template +void print(Args&&... args) { + auto a = {(std::cout << std::forward(args) << std::endl, 0)...}; +} + +} // namespace jc + +int main() { jc::print(3.14, 42, std::string{"hello"}, "world"); } +``` + +* C++11 引入了 [sizeof...](https://en.cppreference.com/w/cpp/language/sizeof...) 在编译期计算参数包中的元素数,C++17 引入了 if constexpr 判断编译期值,编译期结果为 true 才会实例化代码 + +```cpp +#include +#include +#include + +namespace jc { + +template +void print(const T& t, Args&&... args) { + std::cout << t << std::endl; + if constexpr (sizeof...(args) > 0) { // 不能用 if,因为零长包也会实例化代码 + print(std::forward(args)...); // 当条件满足时才实例化 + } +} + +} // namespace jc + +int main() { jc::print(3.14, 42, std::string{"hello"}, "world"); } +``` + +* 在 C++11 中可以利用偏特化来达到 if constexpr 的效果 + +```cpp +#include +#include +#include + +namespace jc { + +template +struct A; + +template +void print(const T& t, Args&&... args) { + std::cout << t << std::endl; + A<(sizeof...(args) > 0)>::f(std::forward(args)...); +} + +template +struct A { + template + static void f(Args&&... args) { + print(std::forward(args)...); + } +}; + +template <> +struct A { + static void f(...) {} +}; + +} // namespace jc + +int main() { jc::print(3.14, 42, std::string{"hello"}, "world"); } +``` + +## [折叠表达式(Fold Expression)](https://en.cppreference.com/w/cpp/language/fold) + +* C++17 引入了折叠表达式,用于获取对所有参数包实参使用二元运算符的计算结果 + +```cpp +#include +#include +#include + +namespace jc { + +template +auto sum(Args&&... args) { + auto a = (... + std::forward(args)); // (((1 + 2) + 3) + 4) + auto b = (std::forward(args) + ...); // (1 + (2 + (3 + 4))) + auto c = (5 + ... + std::forward(args)); // ((((5 + 1) + 2) + 3) + 4) + auto d = (std::forward(args) + ... + 5); // (1 + (2 + (3 + (4 + 5)))) + return std::make_tuple(a, b, c, d); +} + +auto print1 = [](auto&&... args) { + // operator<< 左折叠,std::cout 是初始值 + (std::cout << ... << std::forward(args)); +}; + +auto print2 = [](auto&&... args) { + // operator, 左折叠 + ((std::cout << std::forward(args) << ","), ...); +}; + +} // namespace jc + +int main() { + auto [a, b, c, d] = jc::sum(1, 2, 3, 4); + jc::print1(a, b, c, d); // 10101515 + jc::print2(a, b, c, d); // 10,10,15,15, +} +``` + +* 对于空扩展需要决定类型和值,空的一元折叠表达式通常会产生错误,除了三种例外情况 + * 一个 `&&` 的一元折叠的空扩展产生值 true + * 一个 `||` 的一元折叠的空扩展产生值 false + * 一个 `,` 的一元折叠空扩展产生一个 void 表达式 + +|折叠表达式|计算结果| +|:-:|:-:| +|(... op pack)|(((pack1 op pack2) op pack3) ... op PackN)| +|(pack op ...)|(pack1 op (... (packN-1 op packN)))| +|(init op ... op pack)|(((init op pack1) op pack2) ... op PackN)| +|(pack op ... op init)|(pack1 op (... (packN op init)))| + +* 折叠表达式借鉴的是 [Haskell](https://www.haskell.org/) 的 fold + +```hs +import Data.List (foldl') + +foldlList :: [Char] +foldlList = foldl' (\x y -> concat ["(", x, "+", y, ")"]) "0" (map show [1 .. 4]) + +foldrList :: [Char] +foldrList = foldr ((\x y -> concat ["(", x, "+", y, ")"]) . show) "0" [1 .. 4] + +main :: IO () +main = do + putStrLn foldlList -- ((((0+1)+2)+3)+4) + putStrLn foldrList -- (1+(2+(3+(4+0)))) +``` + +* 实现与 Haskell 类似的左折叠和右折叠 + +```cpp +#include +#include +#include + +namespace jc { + +template +void foldlList(F&& f, T&& zero, Args&&... x) { + ((std::invoke(std::forward(f), (std::string(sizeof...(Args), '('))), + std::invoke(std::forward(f), (std::forward(zero)))), + ..., + (std::invoke(std::forward(f), ('+')), + std::invoke(std::forward(f), (std::forward(x))), + std::invoke(std::forward(f), (')')))); +} + +template +void foldrList(F&& f, T&& zero, Args&&... x) { + ((std::invoke(std::forward(f), ('(')), + std::invoke(std::forward(f), (std::forward(x))), + std::invoke(std::forward(f), ('+'))), + ..., + (std::invoke(std::forward(f), (std::forward(zero))), + std::invoke(std::forward(f), (std::string(sizeof...(Args), ')'))))); +} + +} // namespace jc + +int main() { + auto print = [](const auto& x) { std::cout << x; }; + jc::foldlList(print, 0, 1, 2, 3, 4); // ((((0+1)+2)+3)+4) + jc::foldrList(print, 0, 1, 2, 3, 4); // (1+(2+(3+(4+0)))) +} +``` + +* 折叠表达式几乎可以使用所有二元运算符 + +```cpp +#include + +namespace jc { + +struct Node { + Node(int i) : val(i) {} + + int val = 0; + Node* left = nullptr; + Node* right = nullptr; +}; + +// 使用 operator->* 的折叠表达式,用于遍历指定的二叉树路径 +template +Node* traverse(T root, Args... paths) { + return (root->*...->*paths); // root ->* paths1 ->* paths2 ... +} + +void test() { + Node* root = new Node{0}; + root->left = new Node{1}; + root->left->right = new Node{2}; + root->left->right->left = new Node{3}; + + auto left = &Node::left; + auto right = &Node::right; + Node* node1 = traverse(root, left); + assert(node1->val == 1); + Node* node2 = traverse(root, left, right); + assert(node2->val == 2); + Node* node3 = traverse(node2, left); + assert(node3->val == 3); +} + +} // namespace jc + +int main() { jc::test(); } +``` + +* 包扩展可以用于编译期表达式 + +```cpp +#include + +namespace jc { + +template +constexpr bool is_homogeneous(T, Args...) { + return (std::is_same_v && ...); // operator&& 的折叠表达式 +} + +} // namespace jc + +static_assert(!jc::is_homogeneous(3.14, 42, "hello", "world")); +static_assert(jc::is_homogeneous("hello", "", "world")); + +int main() {} +``` + +## 变参模板的应用 + +* 无需指定类型,自动获取 [std::variant](https://en.cppreference.com/w/cpp/utility/variant) 值 + +```cpp +#include +#include +#include +#include +#include +#include + +namespace jc { + +template +constexpr auto make_array_impl(F f, std::index_sequence) + -> std::array, sizeof...(N)> { + return {std::invoke(f, std::integral_constant{})...}; +} + +template +constexpr auto make_array(F f) + -> std::array, N> { + return make_array_impl(f, std::make_index_sequence{}); +} + +template +bool get_value_impl(const std::variant& v, Dst& dst) { + if (std::holds_alternative(v)) { + if constexpr (std::is_convertible_v) { + dst = static_cast(std::get(v)); + return true; + } + } + return false; +} + +template +bool get_value(const std::variant& v, Dst& dst) { + using Variant = std::variant; + using F = std::function; + static auto _list = make_array([](auto i) -> F { + return &get_value_impl, Dst, + List...>; + }); + return std::invoke(_list[v.index()], v, dst); +} + +} // namespace jc + +int main() { + std::variant v = std::string{"test"}; + std::string s; + assert(jc::get_value(v, s)); + assert(s == "test"); + v = 42; + int i; + assert(jc::get_value(v, i)); + assert(i == 42); +} +``` + +* 字节序转换 + +```cpp +// https://en.cppreference.com/w/cpp/language/fold + +#include +#include +#include + +namespace jc { + +template +constexpr T bswap_impl(T t, std::index_sequence) { + return (((t >> N * 8 & 0xFF) << (sizeof(T) - 1 - N) * 8) | ...); +} + +template > +constexpr U bswap(T t) { + return bswap_impl(t, std::make_index_sequence{}); +} + +} // namespace jc + +static_assert(jc::bswap(0x12345678u) == 0x78563412u); +static_assert((0x12345678u >> 0) == 0x12345678u); +static_assert((0x12345678u >> 8) == 0x00123456u); +static_assert((0x12345678u >> 16) == 0x00001234u); +static_assert((0x12345678u >> 24) == 0x00000012u); +static_assert(jc::bswap(0x1234u) == 0x3412u); + +int main() {} +``` + +* [自定义字面值(User-defined literals)](https://en.cppreference.com/w/cpp/language/user_literal) + +```cpp +#include +#include +#include +#include +#include +#include +#include + +namespace jc { + +template +std::string operator"" _dbg() { + std::array v{args...}; + std::stringstream os; + for (const auto& x : v) { + os << x; + }; +#ifndef NDEBUG + std::cout << os.str() << std::endl; +#endif + return os.str(); +} + +std::string operator"" _encrypt(const char* c, size_t) { + std::string s{c}; + std::string p{R"(passwd: ")"}; + auto it = std::search(s.begin(), s.end(), + std::boyer_moore_horspool_searcher{p.begin(), p.end()}); + if (it != s.end()) { + it += p.size(); + while (it != s.end() && *it != '\"') { + *it++ = '*'; + } + } +#if !defined(NDEBUG) + std::cout << s << std::endl; +#endif + return s; +} + +} // namespace jc + +int main() { + using namespace jc; + + assert(12.34_dbg == "12.34"); + + std::string s = R"JSON({ + data_dir: "C:\Users\downdemo\.data\*.txt", + user: "downdemo(accelerate rapidly)", + passwd: "123456" +})JSON"_encrypt; + + std::string s2 = R"JSON({ + data_dir: "C:\Users\downdemo\.data\*.txt", + user: "downdemo(accelerate rapidly)", + passwd: "******" +})JSON"; + + assert(s == s2); +} +``` + +* 变参基类 + +```cpp +#include +#include + +namespace jc { + +struct A { + std::string s; +}; + +struct A_EQ { + bool operator()(const A& lhs, const A& rhs) const { return lhs.s == rhs.s; } +}; + +struct A_Hash { + std::size_t operator()(const A& a) const { + return std::hash{}(a.s); + } +}; + +// 定义一个组合所有基类的 operator() 的派生类 +template +struct Overloader : Bases... { + using Bases::operator()...; // C++17 +}; + +using A_OP = Overloader; + +} // namespace jc + +int main() { + // 将 A_EQ 和 A_Hash 组合到一个类型中 + + /* unordered_set 的声明 + template< + class Key, +     class Hash = std::hash, +     class KeyEqual = std::equal_to, +     class Allocator = std::allocator + > class unordered_set; + */ + + std::unordered_set s1; + std::unordered_set s2; +} +``` + +* C++14 使用 [std::integer_sequence](https://en.cppreference.com/w/cpp/utility/integer_sequence) 遍历 [std::tuple](https://en.cppreference.com/w/cpp/utility/tuple) + +```cpp +#include +#include +#include +#include +#include +#include + +namespace jc { + +template +void apply_impl(F&& f, const std::tuple& t, + std::index_sequence) { + std::invoke(std::forward(f), std::get(t)...); +} + +template +void apply(F&& f, const std::tuple& t) { + apply_impl(std::forward(f), t, std::index_sequence_for{}); +} + +} // namespace jc + +struct Print { + template + void operator()(const Args&... args) { + auto no_used = {(std::cout << args << " ", 0)...}; + } +}; + +int main() { + auto t = std::make_tuple(3.14, 42, "hello world"); + jc::apply(Print{}, t); // 3.14 42 hello world +} +``` + +* C++11 未提供 [std::integer_sequence](https://en.cppreference.com/w/cpp/utility/integer_sequence),手动实现一个即可 + +```cpp +#include +#include +#include +#include +#include +#include + +namespace jc { + +template +struct index_sequence { + using type = index_sequence; +}; + +template +struct concat; + +template +struct concat, index_sequence> + : index_sequence {}; + +template +using concat_t = typename concat::type; + +template +struct make_index_sequence_impl + : concat_t::type, + typename make_index_sequence_impl::type> {}; + +template <> +struct make_index_sequence_impl<0> : index_sequence<> {}; + +template <> +struct make_index_sequence_impl<1> : index_sequence<0> {}; + +template +using make_index_sequence = typename make_index_sequence_impl::type; + +template +using index_sequence_for = make_index_sequence; + +static_assert(std::is_same_v, index_sequence<0, 1, 2>>); + +template +void apply_impl(F&& f, const std::tuple& t, index_sequence) { + std::invoke(std::forward(f), std::get(t)...); +} + +template +void apply(F&& f, const std::tuple& t) { + apply_impl(std::forward(f), t, index_sequence_for{}); +} + +} // namespace jc + +struct Print { + template + void operator()(const Args&... args) { + auto no_used = {(std::cout << args << " ", 0)...}; + } +}; + +int main() { + auto t = std::make_tuple(3.14, 42, "hello world"); + jc::apply(Print{}, t); // 3.14 42 hello world +} +``` diff --git a/docs/05_move_semantics_and_perfect_forwarding.md b/docs/05_move_semantics_and_perfect_forwarding.md new file mode 100644 index 0000000..595c6a2 --- /dev/null +++ b/docs/05_move_semantics_and_perfect_forwarding.md @@ -0,0 +1,346 @@ +## 移动语义(Move Semantics) + +* C++11 的[值类别](https://en.cppreference.com/w/cpp/language/value_category)包括左值(lvalue)、纯右值(prvalue)、亡值(xvalue),左值和亡值组成了泛左值(glvalue),纯右值和亡值组成了右值(rvalue)。为了让编译器识别接受右值作为参数的构造函数,则需要引入右值引用符号(&&),以区分移动构造函数和拷贝构造函数 + +```cpp +#include +#include +#include +#include + +namespace jc { + +struct A { + A() : data(new std::string) {} + A(const A& rhs) : data(new std::string{*rhs.data}) {} + A(A&& rhs) noexcept : data(rhs.data) { rhs.data = nullptr; } + ~A() { delete data; } + + std::string* data = nullptr; +}; + +} // namespace jc + +int main() { + std::vector v; + v.emplace_back(jc::A{}); // 调用默认构造函数、移动构造函数、析构函数 + jc::A a; + v.emplace_back(a); // 调用拷贝构造函数 + assert(a.data); + v.emplace_back(std::move(a)); // 调用移动构造函数 + assert(!a.data); +} +``` + +* 右值引用即只能绑定到右值的引用,字面值(纯右值)和临时变量(亡值)就是常见的右值。如果把左值传递给右值引动参数,则需要强制类型转换,[std::move](https://en.cppreference.com/w/cpp/utility/move) 就是不需要显式指定类型的到右值引用的强制类型转换 + +```cpp +#include +#include +#include +#include + +namespace jc { + +template +constexpr std::remove_reference_t&& move(T&& x) noexcept { + return static_cast&&>(x); +} + +constexpr int f(const std::string&) { return 1; } +constexpr int f(std::string&&) { return 2; } + +} // namespace jc + +int main() { + std::string s; + static_assert(jc::f(s) == 1); + assert(jc::f(std::string{}) == 2); + static_assert(jc::f(static_cast(s)) == 2); + static_assert(jc::f(jc::move(s)) == 2); + static_assert(jc::f(std::move(s)) == 2); +} +``` + +## 完美转发(Perfect Forwarding) + +* 右值引用是能接受右值的引用,引用可以取址,是左值,因此右值引用是左值。如果一个函数接受右值引用参数,把参数传递给其他函数时,会按左值传递,这样就丢失了原有的值类别 + +```cpp +#include +#include +#include + +namespace jc { + +constexpr int f(const std::string&) { return 1; } +constexpr int f(std::string&&) { return 2; } +constexpr int g(std::string&& s) { return f(s); } + +void test() { + std::string s; + assert(f(std::string{}) == 2); + assert(g(std::string{}) == 1); + static_assert(f(std::move(s)) == 2); + static_assert(g(std::move(s)) == 1); +} + +} // namespace jc + +int main() { jc::test(); } +``` + +* 为了转发时保持值类别不丢失,需要手写多个重载版本 + +```cpp +#include +#include +#include + +namespace jc { + +constexpr int f(std::string&) { return 1; } +constexpr int f(const std::string&) { return 2; } +constexpr int f(std::string&&) { return 3; } +constexpr int g(std::string& s) { return f(s); } +constexpr int g(const std::string& s) { return f(s); } +constexpr int g(std::string&& s) { return f(std::move(s)); } + +void test() { + std::string s; + const std::string& s2 = s; + static_assert(g(s) == 1); + assert(g(s2) == 2); + static_assert(g(std::move(s)) == 3); + assert(g(std::string{}) == 3); +} + +} // namespace jc + +int main() { jc::test(); } +``` + +* 模板参数中右值引用符号表示的是万能引用(universal reference),因为模板参数本身可以推断为引用,它可以匹配几乎任何类型(少部分特殊类型无法匹配,如位域),传入左值时推断为左值引用类型,传入右值时推断为右值引用类型。对万能引用参数使用 [std::forward](https://en.cppreference.com/w/cpp/utility/forward) 则可以保持值类别不丢失,这种保留值类别的转发手法就叫完美转发,因此万能引用也叫转发引用(forwarding reference) + +```cpp +#include +#include +#include + +namespace jc { + +template +constexpr T&& forward(std::remove_reference_t& t) noexcept { + return static_cast(t); +} + +constexpr int f(std::string&) { return 1; } +constexpr int f(const std::string&) { return 2; } +constexpr int f(std::string&&) { return 3; } + +template +constexpr int g(T&& s) { + return f(jc::forward(s)); // 等价于 std::forward +} + +void test() { + std::string s; + const std::string& s2 = s; + static_assert(g(s) == 1); // T = T&& = std::string& + assert(g(s2) == 2); // T = T&& = const std::string& + static_assert(g(std::move(s)) == 3); // T = std::string, T&& = std::string&& + assert(g(std::string{}) == 3); // T = T&& = std::string& + assert(g("downdemo") == 3); // T = T&& = const char (&)[9] +} + +} // namespace jc + +int main() { jc::test(); } +``` + +* 结合变参模板完美转发转发任意数量的实参 + +```cpp +#include +#include +#include +#include + +namespace jc { + +template +constexpr void constexpr_for(F&& f, Args&&... args) { + (std::invoke(std::forward(f), std::forward(args)), ...); +} + +template +void print(Args&&... args) { + constexpr_for([](const auto& x) { std::cout << x << std::endl; }, + std::forward(args)...); +} + +} // namespace jc + +int main() { jc::print(3.14, 42, std::string{"hello"}, "world"); } +``` + +* [Lambda](https://en.cppreference.com/w/cpp/language/lambda) 中使用完美转发需要借助 decltype 推断类型 + +```cpp +#include +#include +#include +#include + +namespace jc { + +constexpr auto constexpr_for = [](auto&& f, auto&&... args) { + (std::invoke(std::forward(f), + std::forward(args)), + ...); +}; + +auto print = [](auto&&... args) { + constexpr_for([](const auto& x) { std::cout << x << std::endl; }, + std::forward(args)...); +}; + +} // namespace jc + +int main() { jc::print(3.14, 42, std::string{"hello"}, "world"); } +``` + +* C++20 可以为 lambda 指定模板参数 + +```cpp +#include +#include +#include +#include + +namespace jc { + +constexpr auto constexpr_for = + [](F&& f, Args&&... args) { + (std::invoke(std::forward(f), std::forward(args)), ...); +}; + +auto print = [](Args&& ... args) { + constexpr_for([](const auto& x) { std::cout << x << std::endl; }, + std::forward(args)...); +}; + +} // namespace jc + +int main() { jc::print(3.14, 42, std::string{"hello"}, "world"); } +``` + +* C++20 的 lambda 可以捕获参数包 + +```cpp +#include +#include +#include +#include + +namespace jc { + +template +void print(Args&&... args) { + [... args = std::forward(args)](F&& f) { + (std::invoke(std::forward(f), args), ...); + }([](const auto& x) { std::cout << x << std::endl; }); +} + +} // namespace jc + +int main() { jc::print(3.14, 42, std::string{"hello"}, "world"); } +``` + +## 构造函数模板 + +* 模板也能用于构造函数,但它不是真正的构造函数,从函数模板实例化而来的函数不和普通函数等价,由成员函数模板实例化的函数不会重写虚函数,由构造函数模板实例化的构造函数不是拷贝或移动构造函数,但对一个 non-const 对象调用构造函数时,万能引用是更优先的匹配 + +```cpp +#include +#include + +namespace jc { + +struct A { + template + explicit A(T&& t) : s(std::forward(t)) {} + + A(const A& rhs) : s(rhs.s) {} + A(A&& rhs) noexcept : s(std::move(rhs.s)) {} + + std::string s; +}; + +} // namespace jc + +int main() { + const jc::A a{"downdemo"}; + jc::A b{a}; // OK,匹配拷贝构造函数 + // jc::A c{b}; // 错误,匹配模板构造函数 +} +``` + +* 为此可以用 [std::enable_if](https://en.cppreference.com/w/cpp/types/enable_if) 约束模板参数,在条件满足的情况下才会匹配模板 + +```cpp +#include +#include +#include + +namespace jc { + +struct A { + template >> + explicit A(T&& t) : s(std::forward(t)) {} + + A(const A& rhs) : s(rhs.s) {} + A(A&& rhs) noexcept : s(std::move(rhs.s)) {} + + std::string s; +}; + +} // namespace jc + +int main() { + const jc::A a{"downdemo"}; + jc::A b{a}; // OK,匹配拷贝构造函数 + jc::A c{b}; // OK,匹配拷贝构造函数 +} +``` + +* C++20 可以用 [concepts](https://en.cppreference.com/w/cpp/concepts) 约束模板参数 + + +```cpp +#include +#include +#include + +namespace jc { + +struct A { + template + requires std::convertible_to + explicit A(T&& t) : s(std::forward(t)) {} + + A(const A& rhs) : s(rhs.s) {} + A(A&& rhs) noexcept : s(std::move(rhs.s)) {} + + std::string s; +}; + +} // namespace jc + +int main() { + const jc::A a{"downdemo"}; + jc::A b{a}; // OK,匹配拷贝构造函数 + jc::A c{b}; // OK,匹配拷贝构造函数 +} +``` diff --git a/docs/06_name_lookup.md b/docs/06_name_lookup.md new file mode 100644 index 0000000..167d57e --- /dev/null +++ b/docs/06_name_lookup.md @@ -0,0 +1,617 @@ +## [ADL(Argument-Dependent Lookup,Koenig Lookup)](https://en.cppreference.com/w/cpp/language/adl) + +* [Name lookup](https://en.cppreference.com/w/cpp/language/lookup) 是当程序中出现一个名称时,将其与引入它的声明联系起来的过程,它分为 [qualified name lookup](https://en.cppreference.com/w/cpp/language/qualified_lookup) 和 [unqualified name lookup](https://en.cppreference.com/w/cpp/language/lookup),[unqualified name lookup](https://en.cppreference.com/w/cpp/language/lookup) 对于函数名查找会使用 [ADL](https://en.cppreference.com/w/cpp/language/adl) + +```cpp +namespace jc { + +struct A {}; +struct B {}; +void f1(int) {} +void f2(A) {} + +} // namespace jc + +namespace jd { + +void f1(int i) { + f1(i); // 调用 jd::f1(),造成无限递归 +} + +void f2(jc::A t) { + f2(t); // 通过 t 的类型 jc::A 看到 jc,通过 jc 看到 jc::f2() + // 因为 jd::f2() 也可见,此处产生二义性调用错误 +} + +void f3(jc::B t) { + f3(t); // 通过 t 的类型 jc::B 看到 jc,但 jc 中无 jc::f3() + // 此处调用 jd::f3(),造成无限递归 +} + +} // namespace jd + +int main() {} +``` + +* [Qualified name lookup](https://en.cppreference.com/w/cpp/language/qualified_lookup) 即对使用了作用域运算符的名称做查找,查找在受限的作用域内进行 + +```cpp +namespace jc { + +int x; + +struct Base { + int i; +}; + +struct Derived : Base {}; + +void f(Derived* p) { + p->i = 0; // 找到 Base::i + Derived::x = 0; // 错误:在受限作用域中找不到 ::x +} + +} // namespace jc + +int main() {} +``` + +* [Unqualified name lookup](https://en.cppreference.com/w/cpp/language/lookup) 即对不指定作用域的名称做查找,先查找当前作用域,若找不到再继续查找外围作用域 + +```cpp +namespace jc { + +extern int x; // 1 + +int f(int x) { // 2 + if (x < 0) { + int x = 1; // 3 + f(x); // 使用 3 + } + return x + ::x; // 分别使用 2、1 +} + +} // namespace jc + +int main() {} +``` + +* [ADL](https://en.cppreference.com/w/cpp/language/adl) 即实参依赖查找,对于一个类,其成员函数与使用了它的非成员函数,都是该类的逻辑组成部分,如果函数接受一个类作为参数,编译器查找函数名时,不仅会查找局部作用域,还会查找类所在的命名空间 + +```cpp +#include +#include + +namespace jc { + +struct A {}; + +void f(const A&) {} // f() 是 A 的逻辑组成部分 + +} // namespace jc + +jc::A a; + +int main() { + f(a); // 通过 ADL 找到 jc::f(),如果没有 ADL,就要写成 jc::f(a) + std::string s; + std::cout << s; // std::operator<<() 是 std::string 的逻辑组成部分 + // 如果没有 ADL,就要写成 std::operator<<(std::cout, s) +} +``` + +* ADL 是通过实参查找,对于函数模板,查找前无法先得知其为函数,也就无法确定实参,因此不会使用 ADL + +```cpp +namespace jc { + +class A {}; + +template +void f(A*) {} + +} // namespace jc + +void g(jc::A* p) { + f(p); // 错误,不知道 f 是函数,所以不知道 p 是实参,不会用 ADL +} + +int main() {} +``` + +* ADL 会忽略 using 声明 + +```cpp +namespace jc { + +template +constexpr int f(T) { + return 1; +} + +} // namespace jc + +namespace jd { + +using namespace jc; // 忽略 using 声明,不会调用 jc::f + +enum Color { red }; +constexpr int f(Color) { return 2; } + +} // namespace jd + +constexpr int f(int) { return 3; } + +static_assert(::f(jd::red) == 3); // 受限的函数名称,不使用 ADL +static_assert(f(jd::red) == 2); // 使用 ADL 找到 jd::f() + +int main() {} +``` + +* ADL 会查找实参关联的命名空间和类,关联的命名空间和类组成的集合定义如下 + * 内置类型:集合为空 + * 指针和数组类型:所引用类型关联的命名空间和类 + * 枚举类型:关联枚举声明所在的命名空间 + * 类成员:关联成员所在的类 + * 类类型:关联的类包括该类本身、外围类型、直接和间接基类,关联的命名空间为每个关联类所在的命名空间,如果类是一个类模板实例则还包含模板实参本身类型、模板的模板实参所在的类和命名空间 + * 函数类型:所有参数和返回类型关联的命名空间和类 + * 类成员指针类型:成员和类关联的命名空间和类 +* 友元声明在外围作用域不可见,因为如果可见的话,实例化类模板会使普通函数的声明可见,如果没有先实例化类就调用函数,将导致编译错误,但如果友元函数所在类属于 ADL 的关联类集合,则在外围作用域可以找到该友元声明,且调用时,未实例化的类会被实例化 + +```cpp +namespace jc { + +template +class A { + friend void f() {} + friend void f(A) {} +}; + +void g(const A& a) { + f(); // f() 无参数,不能使用 ADL,不可见 + f(a); // f(A) 关联类 A 所以可见,若类 A 未实例化则调用时实例化 +} + +} // namespace jc + +int main() {} +``` + +## [注入类名(Injected Class Name)](https://en.cppreference.com/w/cpp/language/injected-class-name) + +* 为了便于查找,在类作用域中,类名称是自身类型的 public 别名,该名称称为注入类名 + +```cpp +namespace jc { + +int A; + +struct A { + void f() { + A* p; // OK:A 是注入类名 + ::A* q; // 错误:查找到变量名 A,隐藏了 struct A 的名称 + } +}; + +} // namespace jc + +int main() {} +``` + +* 类模板的注入类名可以被用作模板名或类型名 + +```cpp +namespace jc { + +template