详解c++中万能引用、完美转发、类型推导
值继续看这章之前建议先了解右值的概念,可以看c++详解移动语义,右值引用与move函数的作用,noexcept关键字在移动构造与移动赋值运算符中的重要性-CSDN博客右详细介绍右值
万能引用
万能引用是Scott Meyers提出的术语,指能够根据传入参数类型自动推导为左值引用或右值引用的引用类型。其主要有函数模板参数中的T&&
与auto&&
变量声明两种形式,想要实现万能引用类型必须需要进行推导,并且不能有const修饰符或被容器包装
void right_or_left(int&& a){
std::cout << "right(int&& a) called" << std::endl;
}
void right_or_left(int& a){
std::cout << "left(int& a) called" << std::endl;
}
template<typename T>
void func1(T&& t){
std::cout << t << std::endl;
right_or_left(std::forward<T>(t));
}
int main() {
int i = 43;
func1(1);
func1(i);
i = 111;
func1(std::move(i));
return 0;
}
// 输出
1
right(int&& a) called
43
left(int& a) called
111
right(int&& a) called
template<typename T>
void f1(T&& param); // 万能引用
template<typename T>
void f2(std::vector<T>&& param); // 普通右值引用
为什么万能引用既可以是左值引用,也可以是右值引用呢?这就涉及到 C++ 的引用折叠语法了。引用分为左值引用 T& 和右值引用 T&& 两种,那么将这两种引用进行排列组合,就会有四种情况。所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。
规 则就是:如果任一引用为左值引用,则结果为左值引用;否则,结果为右值引用。
T& &
→T&
(左值引用的左值引用折叠为左值引用)T& &&
→T&
(左值引用的右值引用折叠为左值引用)T&& &
→T&
(右值引用的左值引用折叠为左值引用)T&& &&
→T&&
(右值引用的右值引用折叠为右值引用)
完美转发
示例中还使用了完美转发的概念,完美转发是指在函数模板中将参数以原始类型转发给另一个函数,保持其值类别(左值/右值)不变。下面是完美转发的源码:
template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
完美转发的实现中大量使用了折叠引用的概念,forward的参数使用remove_reference_t<_Ty>&
和remove_reference_t<_Ty>&&
的设计,这是为了为不同类型的参数设置不同的处理规制,在右值的实现中static_assert的作用是,防止将左值传递给右值版本的 forward
。当 _Ty
是左值引用时,触发静态断言。
- 当外部传入左值时:
- 假设调用
std::forward<int&>(arg)
:_Ty
被显式指定为int&
(左值引用)。_Ty&&
展开为int& &&
,根据引用折叠规则,折叠为int&
。- 因此,
static_cast<int&>(arg)
将左值arg
转换为左值引用,不改变值类别。 - 返回类型
_Ty&&
折叠为int&
,最终返回左值引用。
- 假设调用
- 当外部传入右值时:
- 假设调用
std::forward<int>(arg)
或std::forward<int&&>(arg)
:_Ty
被推导为int
或显式指定为int&&
(右值引用)。_Ty&&
展开为int&&
(若_Ty
是int
)或int&& &&
→int&&
(若_Ty
是int&&
)。static_cast<int&&>(arg)
将左值arg
强制转换为右值引用,允许后续的移动操作。- 返回类型
_Ty&&
为int&&
,最终返回右值引用。
- 假设调用
类型推导
auto
根据初始化表达式推导变量类型,但会忽略引用和顶层const
。
int x = 10;
const int cx = x;
int& rx = x;
auto a1 = x; // a1 → int
auto a2 = cx; // a2 → int(忽略顶层const)
auto a3 = rx; // a3 → int(忽略引用)
auto a4 = &x; // a4 → int*(指针保留)
decltype(expr)
根据表达式的类型和值类别推导类型,保留所有类型信息(包括引用和const
)。
- 若
expr
是变量名(非表达式),decltype
返回该变量的声明类型(保留引用和const
)。 - 若
expr
是表达式,decltype
返回表达式结果的类型,并根据值类别添加引用:- 左值表达式 →
T&
- 右值表达式 →
T
- 左值表达式 →
int x = 10;
const int cx = x;
int& rx = x;
decltype(x) d1 = x; // d1 → int
decltype(cx) d2 = x; // d2 → const int
decltype(rx) d3 = x; // d3 → int&
decltype(x + 0) d4 = x; // d4 → int(表达式是右值)
decltype((x)) d5 = x; // d5 → int&(括号使表达式变为左值)