一. 模板类型推断
参考:https://blog.youkuaiyun.com/big_yellow_duck/article/details/52140262
template <typename T>
void f(ParamType param);
这里面几个主要的点:
1:通用引用(universal references) type&&
If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.
如果一个变量或者参数被声明为T&&,其中T是被推导的类型,那这个变量或者参数就是一个universal reference。
2:引用类型或者通用引用类型,T和ParamType推导类型有差别。
3: 如果ParamType既不是引用也不是指针类型
template <typename T>
void f(T param); // 此处会有拷贝(构造)
如果传入的参数expr是引用,那么引用会被忽略
如果传入的参数expr有const,volatile修饰符也会被忽略
模板的定义是值传递,所以忽略引用。而且因为是值传递,传入的参数是const,并不意味着他们的拷贝也是const。
4:函数作为参数
如果ParamType是值语义,则其类型推导为函数指针。
如果ParamType是引用语义,则其类型推导为函数引用。
实际使用中区别不大。
二.引用折叠
X& & <=> X&, 左值引用的左值引用等价于左值引用
X& && <=> X&, 左值引用的右值引用等价于左值引用
X&& & <=> X&, 右值引用的左值引用等价于左值引用
X&& && <=> X&&,右值引用的右值引用等价于右值引用
三.重载模板函数调用优先顺序
template<class T>
void func(T&& t)
{
std::cout << "void func1(T&& t)" << std::endl;
}
template<class T>
void func(const T& t)
{
std::cout << "void func1(T& t)" << std::endl;
}
考虑以上两个重载函数的区别。
func(5); // 调用 T&&
int a = 5;
func(a) // 调用T&&
const int b = 5;
func(a) // 调用T&
以上的几种调用情况,如果只有T&&版本,则都调用T&&版本。如果只有T&版本,则都调用T&版本。
在这两种版本重载的情况下,则按照匹配度更高的优先。
根据实验结果总结一下:
- 如果是const类型的变量,则优先匹配const的重载版本。const修饰右值一般不存在。
- 如果上一条区分不出,则左值优先匹配左值版本,右值优先匹配右值版本。
remove_reference
去除类型的引用,将引用类型转化为非引用类型
template <class T>
struct remove_reference {
using type = T;
};
// 左值引用版本
template <class T>
struct remove_reference<T&> {
using type = T;
};
// 右值引用版本
template <class T>
struct remove_reference<T&&> {
using type = T;
};
move
template <class T>
typename remove_reference<T>::type&& move(T&& t) noexcept {
using return_type = typename remove_reference<T>::type&&;
return static_cast<return_type>(t);
}
举个栗子说明:
int a = 0;
int& b = a;
move(b);
根据模板类型推导
T => int&
remove_reference::type => int
return_type => int&&
所以move函数只是做了类型转换,在运行阶段并不做任何事情。
右值会成为可移动的候选者,因此对一个对象使用std::move是告诉编译器,这个对象符合被移动的条件。那就是为什么std::move会有这个名字:很容易指出可能被移动的对象。
forward
std::forward能将一个参数原封不动(包括参数值、左/右值等)地传递给另一个函数,这叫完美转发。
template <class T>
T&& forward(typename remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}
template <class T>
T&& forward(typename remove_reference<T>::type&& t) noexcept {
return static_cast<T&&>(t);
}
对右值引用使用std::move,对通用引用使用std::forward
右值引用和通用引用参数在函数中会默认转化为左值引用,所以转发给其他函数时,需要转换为我们希望的参数类型。
简而言之,当把右值引用转发给其他函数时,右值引用应该无条件转换为右值(借助std::move),因为右值引用总是绑定右值。而当把通用引用转发给其他函数时,通用引用应该有条件地转换为右值(借助std::forward),因为通用引用只是有时候会绑定右值。