完美转发指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
文章目录
- 场景
- 旧的方法
- 新的方法
- 内部实现
- 参考文献
场景
思考下面的代码:
template<typename T>
void function(T t) {otherfunc(t);
}
function() 函数模板中调用了otherfunc()函数。我们想要的完美转发是:
- 如果
function()函数接收到的参数t为左值,那么该函数传递给otherfunc()的参数t也是左值。 - 如果
function()函数接收到的参数t为右值,那么传递给otherfunc()函数的参数t也是右值
显然 function() 函数模板没有实现完美转发,这是因为无论是左值还是右值传递进来,都会当作是左值,因为是非引用类型。
比如 function(10); 传递给 otherfunc() 也是左值而不是我们期望的右值,这在我们期望对左值和右值进行不同处理时会产生问题。
旧的方法
在C++98/03 标准下的 C++ 也可以实现完美转发,只是实现方式比较麻烦。
C++98/03 标准中只有左值引用,可以细分为非 const引用和const引用:
- 非const引用作为函数模板参数,只能接收左值无法接收右值
- const左值引用既可以接收左值,也可以接收右值,但如果内部需要将参数传递给其他函数,需要被调用函数的参数也是 const,否则无法直接传递。
可见能实现转发,但不够"完美"。
#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherfunc(int & t) {cout << "call lvalue\n";
}
void otherfunc(const int & t) {cout << "call rvalue\n";
}//重载函数模板,分别接收左值和右值
//接收右值参数
template <typename T>
void function(const T& t) {otherfunc(t);
}
//接收左值参数
template <typename T>
void function(T& t) {otherfunc(t);
}
int main()
{function(10);//10 是右值int x = 2;function(x);//x 是左值return 0;
}
输出为

左值实参既能匹配 function(T& t) 也能匹配 function(const& t),编译器会选择更合适的 function(T& t)。
新的方法
对于旧的方法,当模板函数有大量参数的情况,可能就需要编写大量的重载函数模板。
在C++11标准中引入了右值引用,通常情况下右值引用只能接收右值,而对于函数模板中使用右值引用语法定义的参数来说,它既可以接收右值,也可以接收左值(称为万能引用)。
因此在C++11标准中实现完美转发,只需要编写如下一个模板函数即可:
template <typename T>
void function(T&& t) {otherdef(t);
}
但是还存在一个问题,如果我们传入的参数是一个左值引用或右值引用的实参,如下所示:
int x = 5;
int& y = x;
function(y); // T为int&
int&& z = 1;
function(z); // T 为int&&
其中, function(y) 实例化的函数为 function(int& && t),由function(z) 实例化的函数为 function(int&& &&t),这在C++98/03是不支持的,而C++11引入了引用折叠规则:
- 当实参为左值或者左值引用(
A&)时,函数模板中T&&将转变为A&,即A& &&=A&。 - 当实参为右值或者右值引用(
A&&)时,函数模板中T&&将转变为A&&,即A&& &&=A&&。
还存在的问题是,在function()函数内部,不管是 T& t 还是 T&& t 其实 t 都是一个左值,因此都会传递到 otherfunc(int& t)。
所以我们需要一种解决方案来处理这个问题,C++11标准里的模板函数 forward() 就是用来解决这个问题,让我们能传递左值/右值属性,例子如下:
#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherfunc(int & t) {cout << "lvalue\n";
}
void otherfunc(const int & t) {cout << "rvalue\n";
}
//实现完美转发的函数模板
template <typename T>
void function(T&& t) {otherfunc(forward<T>(t));
}
int main()
{function(1);int x = 2;function(x);return 0;
}
输出如下,正确传递了左值/右值属性

内部实现
下面简单看下内部实现(MSVC)
_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {return static_cast<_Ty&&>(_Arg);
}_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");return static_cast<_Ty&&>(_Arg);
}
简单地来说就是通过静态的强制类型转换+引用折叠,返回对应的结果。
比如下面的四钟情况:
int x = 2;otherfunc(forward<int>(x)); // 匹配第一个,返回 int&&otherfunc(forward<int>(2)); // 匹配第二个,返回 int&&int& y = x;otherfunc(forward<int&>(y)); // 匹配第一个,返回 int&int&& z = 2;otherfunc(forward<int&&>(z)); // 匹配第一个,返回 int&&,可见右值引用是个左值
它的输出结果如下

参考文献
C++11、C++14、C++17、C++20新特性总结(5万字详解)