《Modern C++ Tutorial》读书笔记

在线阅读:https://changkun.de/modern-cpp/zh-cn/00-preface/

《Modern C++ Tutorial》:https://changkun.de/modern-cpp

GitHub仓库:https://github.com/changkun/modern-cpp-tutorial

欧长坤:https://github.com/changkun

第二章 语言可用性的强化

尾返回类型推导

你可能会思考,在介绍 auto 时,我们已经提过 auto 不能用于函数形参进行类型推导,那么 auto 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:

1
2
3
4
template<typename R, typename T, typename U>
R add(T x, U y) {
return x+y;
}

注意:typename 和 class 在模板参数列表中没有区别,在 typename 这个关键字出现之前,都是使用 class 来定义模板参数的。但在模板中定义有嵌套依赖类型的变量时,需要用 typename 消除歧义

这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 add() 这个函数会做什么样的操作,以及获得一个什么样的返回类型。

在 C++11 中这个问题得到解决。虽然你可能马上会反应出来使用 decltype 推导 x+y 的类型,写出这样的代码:

1
decltype(x+y) add(T x, U y)

但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,xy 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:

1
2
3
4
template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}

令人欣慰的是从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:

1
2
3
4
template<typename T, typename U>
auto add3(T x, U y){
return x + y;
}

可以检查一下类型推导是否正确:

1
2
3
4
5
6
7
8
9
10
// after c++11
auto w = add2<int, double>(1, 2.0);
if (std::is_same<decltype(w), double>::value) {
std::cout << "w is double: ";
}
std::cout << w << std::endl;

// after c++14
auto q = add3<double, int>(1.0, 2);
std::cout << "q: " << q << std::endl;

对于C++14开始支持的直接让普通函数具备返回值推导实现的add3()函数我发现了只能在函数声明函数时就实现函数,如果分开将编译出错。

使用的编译器为MSVC以及g++;测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/* ************************************************************************
> File Name: autoKey.cpp
> Author: James He
> James的成长之路 james-blog.top
> Created Time: 2023年07月25日 星期二 20时24分13秒
> Description:
************************************************************************/
#include <iostream>

using namespace std;

int add(auto x, auto y);
template<typename R, typename T, typename U>
R sub(T a, U b);
template<typename T, typename U>
auto add2(T a, U b) -> decltype(a + b);
template<typename T, typename U>
auto add3(T a, U b);
int main(){
auto i = 5;
cout << i << "(int)\n";
auto j = new auto(10);
cout << *j << "(int*)\n";
cout << add(5, 5) << "(int, int)\n";
cout << add(5.5f, 5.5f) << "(float, float)\n";
int x = sub<int, int, int>(5, 5);
cout << x << " type sub\n";
cout << add2(5.5, 5.5) << " add2(double, double)\n";
cout << add3<int, int>(5, 5) << " add3(int, int)\n";
return 0;
}

int add(auto x, auto y){
if(is_same<decltype(x), int>::value)
cout << "x == int\n";
if(is_same<decltype(x), float>::value)
cout << "x == float\n";
return x + y;
}

template<typename R, typename T, typename U>
R sub(T a, U b){
return a - b;
}

template<typename T, typename U>
auto add2(T a, U b) -> decltype(a + b){
return a + b;
}

template<typename T, typename U>
auto add3(T a, U b){
return a + b;
}

g++将报错如下:

1
2
3
4
5
james@james-virtual-machine:~/C++/vscode/modernC++$ g++ autoKey2.cpp -std=c++20
autoKey2.cpp: In function ‘int main()’:
autoKey2.cpp:29:27: error: use of ‘auto add3(T, U) [with T = int; U = int]’ before deduction of ‘auto’
29 | cout << add3<int, int>(5, 5) << " add3(int, int)\n";
| ~~~~~~~~~~~~~~^~~~~~

将函数实现在声明时实现将顺利通过编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* ************************************************************************
> File Name: autoKey.cpp
> Author: James He
> James的成长之路 james-blog.top
> Created Time: 2023年07月25日 星期二 20时24分13秒
> Description:
************************************************************************/
#include <iostream>

using namespace std;

int add(auto x, auto y);
template<typename R, typename T, typename U>
R sub(T a, U b);
template<typename T, typename U>
auto add2(T a, U b) -> decltype(a + b);
template<typename T, typename U>
auto add3(T a, U b){
return a + b;
}
int main(){
auto i = 5;
cout << i << "(int)\n";
auto j = new auto(10);
cout << *j << "(int*)\n";
cout << add(5, 5) << "(int, int)\n";
cout << add(5.5f, 5.5f) << "(float, float)\n";
int x = sub<int, int, int>(5, 5);
cout << x << " type sub\n";
cout << add2(5.5, 5.5) << " add2(double, double)\n";
cout << add3<int, int>(5, 5) << " add3(int, int)\n";
return 0;
}

int add(auto x, auto y){
if(is_same<decltype(x), int>::value)
cout << "x == int\n";
if(is_same<decltype(x), float>::value)
cout << "x == float\n";
return x + y;
}

template<typename R, typename T, typename U>
R sub(T a, U b){
return a - b;
}

template<typename T, typename U>
auto add2(T a, U b) -> decltype(a + b){
return a + b;
}

该封面图片由emetelev9tvPixabay上发布