Browse Source

增加 concept 的内容。

wuye9036 2 years ago
parent
commit
6282afe56c
1 changed files with 104 additions and 2 deletions
  1. 104 2
      ReadMe.md

+ 104 - 2
ReadMe.md

@@ -2365,7 +2365,9 @@ foo(
     int // 这里都不需要 substitution
 )
 {
-  // 整个实现部分,都没有 substitution。这个很关键。
+  // 根据定义,substitution只发生在函数签名上。
+  // 故而整个函数实现部分都不会存在 substitution。
+  // 这是一个重点需要记住。
 }
 ```
 
@@ -2631,7 +2633,105 @@ void foo(
 
 虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++11来说,已经是最好的选择了。
 
-(补充例子:构造函数上的enable_if)
+### Concept “概念”
+
+#### “概念” 解决了什么问题
+从上一节可以看出,我们兜兜转转了那么久,就是为了解决两个问题:
+
+1. 在模板进行特化的时候,盘算一下并告诉编译器这里能不能特化;
+
+2. 在函数决议面临多个候选的时候,如果有且仅有其中一个原型能够被函数决议接纳,那就决定是你了!
+
+如果能够直接表达给编译器,不就不用这么麻烦了么。其实在很多现代语言中,都有类似的语言要素存在,比如C#的约束(constraint on type parameters):
+``` C#
+public class Employee {
+  // ...
+}
+
+public class GenericList<T> where T : Employee {
+  // ...
+}
+```
+上例就非常清晰的呈现了我们对`GenericList`中`T`的要求是:它得是一个`Employee`或`Employee`的子类。
+
+这种“清晰的”类型约束,在C++中称作概念(Concept)。最早有迹可循的概念相关工作应当从2003年后就开始了。2006年Bjarne在POPL 06上的一篇报告“Specifying C++ concepts”算是“近代”Concept工作的首次公开亮相。委员会为Concept筹划数年,在2008年提出了第一版Concepts提案,试图进入C++0x的标准中。这也是Concept第一次在C++社群当中被广泛“炒作”。不过2009年的会议,让“近代”Concept在N2617草案戛然而止。
+
+2013年之后,Concept改头换面为Concept Lite提案(N3701)卷土重来,历经多方博弈和多轮演化,最终形成了我们在C++20里看到的Concept。有关于Concept的方法论和比较,B.S. 在白皮书中有过比较详细的交代。
+
+总之,在concept进入标准之后,模板特化的类型约束写起来就方便与直接多了。而且这些约束之间还可以像表达式一样复用和组合。虽然因为C++类型系统自身的琐碎导致基础库中的concept仍然相当的冗长,但是比起之前起码具备了可用性。
+
+比如我们拿上一节中最后一个例子作为对比:
+``` C++
+// SFINAE
+template <typename ArgT>
+void foo(
+    ArgT&& a, 
+    typename std::enabled_if<
+        std::is_same<std::decay_t<ArgT>, float>::value
+    >::type* = nullptr
+);
+// Concept
+template <typename ArgT>
+  requires std::same_as<std::remove_cvref<T>, float> 
+void foo(ArgT&& a)  {
+}
+```
+可以看到,concept之后的表达式消除了语法噪音,显得更为简洁一些。而对于之前++的例子,concept下则更为扼要:
+```C++
+template <typename T> concept Incrementable = requires (T t) { ++t; }
+template <Incrementable T>
+void inc_counter(T& intTypeCounter) { 
+    ++intTypeCounter;
+}
+```
+直接告诉编译器,我们对T的要求是你得有++。
+
+当然有人会问,那能不能直接写成以下形式,不是更简单吗
+
+``` C++
+template <typename T> requires (T t) { ++t; }
+void inc_counter(T& cnt);
+```
+
+答案是不能。
+因为requires作为keyword是存在二义性的。当它用于模板函数或者模板类的声明时,它是一个constraint,后面需要跟着concept表达式;而用于concept中,则是一个required expression,用于concept的求解。既然constraint后面跟着一个concept表达式,而requires也可以用来定义一个concept expression,那么一个风骚的想法形成了:我能不能用 requires (requires (T t) {++t;}) 来约束模板函数的类型呢?
+
+当然是可以的!C++就是这么的简(~~有~~)单(~~病~~)!
+
+``` C++
+template <typename T> requires (requires (T t) { ++t; })
+void inc_counter(T& cnt);
+```
+
+总而言之,除了这些烦人的问题,“概念”的出现,使得模板的出错提示也清爽了些许 —— 虽然大佬们都在鼓吹concept让模板出错多么好调试,但是实际上模板出错,有一半是来源自类型系统本质上的复杂性,概念并不能解决这一问题。
+
+比如这里使用SFINAE的提示
+```
+<source>:23:5: error: no matching function for call to 'Inc'
+    Inc(y);
+    ^~~
+<source>:5:6: note: candidate template ignored: substitution failure [with T = X]: cannot increment value of type 'X'
+void Inc(T& v, std::decay_t<decltype(++v)>* = nullptr)
+     ^                               ~~
+```
+
+而这里是使用了concept的提示。
+```
+<source>:25:5: error: no matching function for call to 'Inc_Concept'
+    Inc_Concept(y);
+    ^~~~~~~~~~~
+<source>:13:6: note: candidate template ignored: constraints not satisfied [with T = X]
+void Inc_Concept(T& v)
+     ^
+<source>:12:11: note: because 'X' does not satisfy 'Incrementable'
+template <Incrementable T>
+          ^
+<source>:10:41: note: because '++t' would be invalid: cannot increment value of type 'X'
+concept Incrementable = requires(T t) { ++t; };
+```
+
+虽然看起来要更长一点,但是对于复杂类型来说,还是会友善许多。以后会找个例子给大家陈述。
+
 
 ## !!! 以下章节未完成 !!!
 
@@ -2643,6 +2743,8 @@ void foo(
 ### 4.4 “快速”排序
 ### 4.5 其它常用的“轮子”
 
+## 非模板的编译期计算
+
 ## 5 模板的进阶技巧
 ### 5.1 嵌入类
 ### 5.2 Template-Template Class