diff --git a/ReadMe.md b/ReadMe.md index 1f4c65a..1e009f6 100644 --- a/ReadMe.md +++ b/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 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 +void foo( + ArgT&& a, + typename std::enabled_if< + std::is_same, float>::value + >::type* = nullptr +); +// Concept +template + requires std::same_as, float> +void foo(ArgT&& a) { +} +``` +可以看到,concept之后的表达式消除了语法噪音,显得更为简洁一些。而对于之前++的例子,concept下则更为扼要: +```C++ +template concept Incrementable = requires (T t) { ++t; } +template +void inc_counter(T& intTypeCounter) { + ++intTypeCounter; +} +``` +直接告诉编译器,我们对T的要求是你得有++。 + +当然有人会问,那能不能直接写成以下形式,不是更简单吗 + +``` C++ +template 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 requires (requires (T t) { ++t; }) +void inc_counter(T& cnt); +``` + +总而言之,除了这些烦人的问题,“概念”的出现,使得模板的出错提示也清爽了些许 —— 虽然大佬们都在鼓吹concept让模板出错多么好调试,但是实际上模板出错,有一半是来源自类型系统本质上的复杂性,概念并不能解决这一问题。 + +比如这里使用SFINAE的提示 +``` +:23:5: error: no matching function for call to 'Inc' + Inc(y); + ^~~ +:5:6: note: candidate template ignored: substitution failure [with T = X]: cannot increment value of type 'X' +void Inc(T& v, std::decay_t* = nullptr) + ^ ~~ +``` + +而这里是使用了concept的提示。 +``` +:25:5: error: no matching function for call to 'Inc_Concept' + Inc_Concept(y); + ^~~~~~~~~~~ +:13:6: note: candidate template ignored: constraints not satisfied [with T = X] +void Inc_Concept(T& v) + ^ +:12:11: note: because 'X' does not satisfy 'Incrementable' +template + ^ +: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