以下是海贼班上的学习笔记,加上自己的一些心得。
下面的swap代码可以正确运行。
#include <iostream>
using namespace std;
namespace haizei {
template<typename T>
void swap(T &a, T &b) {
T c;
c = a; a = b; b = c;
return ;
}
} // end of haizei
int main() {
int n = 123, m = 456;
haizei::swap(n, m);
cout << n << " " << m << endl; //输出456, 123
return 0;
}
但是如果我们想调用haizei::swap(789, n)来把789的值赋给n,编译器出错。因为789是右值,不能绑定在左值引用的行参。
如果我们写成下面的形式,还是不对。
#include <iostream>
using namespace std;
namespace haizei {
template<typename T>
void swap(T &&a, T &&b) {
T c;
c = a; a = b; b = c;
return ;
}
} // end of haizei
int main() {
int n = 123, m = 456;
haizei::swap(n, m);
cout << n << " " << m << endl;
haizei::swap(789, n);
return 0;
}
注意,一般函数的参数里面,如果有int &&,那么这个表示右值引用。但是在模板函数里面,如果有T&说明是强制左值引用,但如果是T&&,那么这只是告诉编译器这里是参数的引用,可能是左值引用,也可能是右值引用。
在haizei::swap(n, m)里面,n是int,是左值, 所以T &&被推导成左值引用。那么如果T表示int的话就不对了,因为T&&就会是int &&,那就是右值引用了。所以T只能被推导成int &,那么T&&就是int &&&,注意这里发生了引用折叠,int &&&被折叠成int &.
所以T c;被推导成int &c; 而c又没初始化,所以出错。
再看一下haizei::swap(789, n);这一行,n是int,是左值。所以T也是推导成int &。789是右值,所以T &&表示右值引用,T被推导成int。那么就矛盾了。
如果我们把swap()函数写成下面的形式,那么haizei::swap(n, m)这一行编译是OK了,但是haizei::swap(789, n); 这一行编译还是不行,因为上面提到的T推导的矛盾。
而且虽然haizei::swap(n, m)编译通过了,但是实际上没用。,因为T c = a; 实际上是int & c = a, 这里c和a是一个变量,没法完成交换功能。打印结果会是456, 456。
template<typename T>
void swap(T &&a, T &&b) {
T c = a;
a = b; b = c;
return ;
}
解决办法1: 去掉相关类型的引用,remove_reference<T>。所以T就被推导成int,而不是int &了。下面的程序可以正确对haizei::swap(n,m)进行交换。但是haizei::swap(789, n)还是通不过编译。我个人是觉得编译器还没进到remove_reference这一步,光看到swap的两个参数T&&a, T&&b一个推导成int,一个推导成int& 就觉得不对劲了。
#include <iostream>
using namespace std;
namespace haizei {
template<typename T>
void swap(T &&a, T &&b) {
typename remove_reference<T>::type c;
c = a; a = b; b = c;
return ;
}
} // end of haizei
int main() {
int n = 123, m = 456;
haizei::swap(n, m);
cout << n << " " << m << endl;
// haizei::swap(789, n);
return 0;
}
注意上面的swap()也可以直接写成
template<typename T>
void swap(T &&a, T &&b) {
auto c = a; a = b; b = c;
return ;
}
这里用auto能解掉引用,更简便。
最后,haizei::swap(789, n); 这行怎么办呢?只能采用2个模板参数了,因为789是右值,n是左值,只有一个模板参数T,编译器怎么推导都不对。
#include <iostream>
using namespace std;
namespace haizei {
template<typename T, typename U>
void swap(T &&a, U &&b) {
// typename remove_reference<T>::type c;
auto c = a; a = b; b = c;
return ;
}
} // end of haizei
int main() {
int n = 123, m = 456;
haizei::swap(n, m);
cout << n << " " << m << endl;
haizei::swap(789, n);
cout << n << endl;
return 0;
}
上面的代码可以正确运行。输出结果
456 123
789
把swap()函数改写成
template<typename T, typename U>
void swap(T &&a, U &&b) {
typename remove_reference<T>::type c;
c = a; a = b; b = c;
return ;
}
效果也是一样的。