概览
在本节课中,我们将概述 C++20 所需掌握的核心概念。
在 C++20 之前,我们有两种截然不同的方式来思考函数或用户自定义类型(类)。函数或类可以针对特定类型定义,也可以针对泛型类型定义。在后一种情况下,我们称之为函数模板或类模板。
在讨论 C++ 中的函数时,存在两个极端。让我们仔细分析两种函数实现:
过于具体 vs 过于泛化
### 过于具体
1#include <iostream> 2 3void needInt(int i){ // 1 4 std::cout << i << std::endl; 5} 6 7int main(){ 8 double d{1.234}; 9 needInt(d); 10}
### 过于泛化
```c++
#include template int main(){
std::cout << gcd(100, 10) << std::endl;
std::cout << gcd(3.5, 4.0) << std::endl;
} 我们假设这并不是我们期望的行为。我们从一个 但转换也可以反向进行。 我们定义了一个用户自定义类型 现在,当需要 由于第 8 行的 泛型函数或类可以接受任意值作为参数。如果传入的值不满足函数或类的要求,编译器会在编译时报告错误。 这个错误信息的问题在哪里? 当然,它非常长且难以理解,但我们真正关心的是另一个问题。编译失败的原因是 这时,概念(concepts)便能为我们解困。 借助概念,我们可以获得一种中间状态。我们可以定义作用于语义类别上的函数或类。也就是说,函数或类的参数既不是过于具体,也不是过于泛化,而是具有明确名称的一组要求,例如 在下一节中,我们将了解概念的历史,并探讨概念的未来。1</div>
2</div>
3
4## 过于具体
5
6为每种具体类型都定义一个函数或类是一项繁琐的工作。为了避免这种负担,类型转换常常被用来缓解问题,但这也带来了新的隐患。
7
8### 截断转换
9
10我们有一个函数 `needInt(int a)`(第 3 行),它可以接受一个 `double` 类型的参数。此时会发生截断转换。
11
12```c++
13#include <iostream>
14
15void needInt(int i){
16 std::cout << i << std::endl;
17}
18
19int main(){
20 double d{1.234};
21 needInt(d);
22}double 开始,最终却得到了一个 int。整数提升
MyHouse。MyHouse 的实例可以通过两种方式构造:当在第 5 行不带参数调用时,其成员 family 被设置为空字符串,表示房子还是空的。为了快速判断房子是否为空,我们在第 8 行实现了 bool 类型的转换运算符。1#include <iostream>
2#include <string>
3
4struct MyHouse{
5 MyHouse() = default;
6 MyHouse(const std::string& fam): family(fam) {}
7
8 operator bool() { return !family.empty(); }
9
10 std::string family = "";
11};
12
13void needInt(int i){
14 std::cout << "int: " << i << std::endl;
15}
16
17int main(){
18 std::cout << std::boolalpha << std::endl;
19
20 MyHouse firstHouse;
21 if (!firstHouse){
22 std::cout << "The firstHouse is still empty." << std::endl;
23 }
24
25 MyHouse secondHouse("grimm");
26 if (secondHouse){
27 std::cout << "Family grimm lives in secondHouse." << std::endl;
28 }
29
30 std::cout << std::endl;
31
32 needInt(firstHouse);
33 needInt(secondHouse);
34
35 std::cout << std::endl;
36}int 类型时,MyHouse 的实例也可以被使用。这很奇怪!bool 重载运算符,MyHouse 的实例可以被当作 int 使用,因此也能参与算术表达式:auto res = MyHouse() + 5。这并非我们的本意!不过这里仅作完整说明。从 C++11 开始,我们应该将这类转换运算符声明为 explicit,以避免隐式转换的发生。过于泛化
1#include <iostream>
2
3template<typename T>
4T gcd(T a, T b){
5 if (b == 0) return a;
6 else return gcd(b, a % b);
7}
8
9int main(){
10 std::cout << std::endl;
11
12 std::cout << gcd(100, 10) << std::endl;
13
14 std::cout << gcd(3.5, 4.0) << std::endl;
15 std::cout << gcd("100", "10") << std::endl;
16
17 std::cout << std::endl;
18}double 和 C 字符串都不支持 % 操作符。这意味着错误源于对 double 和字符串类型的模板实例化失败。这种情况发生得太晚了,因此非常糟糕。对于 double 或 C 字符串类型,根本就不应该允许进行模板实例化。对参数的要求应当成为函数声明的一部分,而不是模板实例化失败的副作用。概念拯救我们
Integral。优势