【C++】引用折叠、万能引用和完美转发
1.【C++】const与constexpr2.【C++】vector3.【C++】创建对象写法4.【C++】域作用符的作用5.【C++】map6.【C++】类7.【C++】NULL与nullptr的区别8.【C++】cmath9.【C++】struct10.【C++】string11.【C++】unordered_set12.【C++】基本类型13.【C++】algorithm14.【C++】命名空间15.【C++】缺省参数16.【C++】函数重载17.【C++】引用18.【C++】auto19.【C++】基于范围for循环20.【C++】存储区21.【C++】左值与右值22.【C++】explicit23.【C++】构造函数类别24.【C++】decltype25.【C++】remove_refrence26.【C++】typeid与RTTI27.【C++】using
28.【C++】引用折叠、万能引用和完美转发
1、三者关系
- 因为【引用折叠】特性,才有了万能引用。
2.【完美转发】的特性是借助【万能引用】以及【forward模板函数】来实现。
2、引用折叠
- 规则一: 当我们将一个左值传给模板函数的右值引用参数(T&&)时, 编译器推断模板类型参数T为的左值引用类型,例如对于int类型时,推断T为int&.
- 规则二:如果我们间接创建了一个引用的引用,则这些引用形成了引用折叠。正常情况下,不能直接创建引用的引用,但是可以间接创建。大部分情况下,引用的引用会折叠为普通的左值引用(T& &、T& &&、 T&& &),右值引用的右值引用,则折叠成右值引用。
举例如下:代码中的函数模板在进行实参推导过程中,T被推导为 int& 类型, int& && 发生引用折叠,最终还是int& 类型。
template<typename T>
void Print(T&& t) {
}
int main()
{
int a = 10;
Print(a);
return 0;
}
3、万能引用
一句话说,就是:即可以绑定到左值引用也可以绑定到右值引用, 并且还会保持左右值的const属性的函数模板参数。形如这样的参数 T&& 就是万能引用。
看下面代码的例子,一眼了然:
template <typename T>
void MyFunc(T&& value) {
}
void main() {
int a = 10;
const int b = 100;
MyFunc(a); // T 为int& 发生引用折叠:int& && ----> int&
MyFunc(b); // T 为const int& 发生引用折叠:constt int& && -----> const int&
MyFunc(100); // T 为int,不发生引用折叠
MyFunc(static_cast<const int&&>(100); // T 为 const int,不发生引用折叠
}
4、完美转发
为什么需要完美转发
本质原因是:右值引用的变量在直接用于作表达式时,被认为是左值变量。 (见此处示意代码中有说明)
举个最简单的例子,下面的代码直接编译报错:
void Func(int&& a) {
}
int main() {
int&& a = 10;
Func(a);
return 0;
}
报错如下:
yin@yin:~$ g++ 1.cpp
1.cpp: In function ‘int main()’:
1.cpp:6:10: error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
Func(a);
^
1.cpp:1:6: note: initializing argument 1 of ‘void Func(int&&)’
void Func(int&& a) {
这会一来会导致什么问题呢,那就是函数模板里调用另一个函数模板时,最外层的的函数模板的参数通常都是万能引用(右值引用,T&&), 传递给最外层的函数模板明明一个右值,然而外层函数模板把参数传递给内层的函数模板时,参数却变成了一个左值, 原参数的属性直接丢失了。看下面的举个例子:
#include <type_traits>
#include <iostream>
using namespace std;
template <typename T>
void Func2(T&& j) {
cout << is_rvalue_reference<T&&>::value << endl;
}
template <typename T>
void Func1(T&& i) {
cout << is_rvalue_reference<T&&>::value << endl;
Func2(i);
}
int main() {
Func1(10);
return 0;
}
输出为如下所示:
yin@yin:~$ ./a.out
1
0
如何解决
借助引用折叠与万能引用的特性,c++11 标准中提供了一个std::forward
#include <type_traits>
#include <iostream>
using namespace std;
template <typename T>
void Func2(T&& j) {
cout << is_rvalue_reference<T&&>::value << endl;
}
template <typename T>
void Func1(T&& i) {
cout << is_rvalue_reference<T&&>::value << endl;
Func2(std::forward<T>(i)); // 注意,此处使用了std::foward<T>();
}
int main() {
Func1(10);
return 0;
}
输出如下, 符合预期,实现完美转发。
yin@yin:~$ ./a.out
1
1
内部实现
想在弄明白原理, 需要结合外层的函数调用(万能引用参数T&&),以及std::forward的内部实现一起来看。
不太多解释,自己看应该明白。 说几点:
- std::remove_reference, 是一个类模板,用于移除类型的引用。具体原型,见后面。
- 这里的std::forward的实现使用了两个重载的函数模板。
std::forward的实现如下(gcc的libstdc++的实现,位于/usr/include/c++/8/bits/move.h文件内):
/**
* @brief Forward an lvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
/**
* @brief Forward an rvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南