mirror of
https://github.com/wuye9036/CppTemplateTutorial.git
synced 2024-03-22 13:11:16 +08:00
增加 concept 的内容。
This commit is contained in:
parent
139ba2ddcb
commit
6282afe56c
106
ReadMe.md
106
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user