C++ std::forward返回值类型推导与重载逻辑相关疑问
核心误区:返回值位置的_Ty&&不属于转发引用
转发引用(旧称通用引用/universal reference)有两个严格成立条件,缺一个都不算:
- 必须出现在函数形参列表中,写法为
T&&,其中T是当前函数的模板参数 - 模板参数
T必须是在本次函数调用时,根据传入实参自动推导得到,不能是调用者显式指定的
std::forward的模板参数_Ty从来不是靠实参推导的——标准调用写法永远是std::forward<T>(arg),_Ty完全由调用者显式传入,因此返回值位置的_Ty&&根本不是转发引用。它最终是左值引用还是右值引用,完全由引用折叠规则和显式传入的_Ty类型决定。
引用折叠规则没有任何例外,核心逻辑非常好记:
只要组合中出现左值引用,最终结果一定是左值引用;只有两个引用全是右值引用时,结果才是右值引用。对应四种组合:
T& &→T&T& &&→T&T&& &→T&T&& &&→T&&
第一个重载支持双向转发的原理
第一个重载的签名对应注释:
forward an lvalue as either an lvalue or an rvalue
它的形参类型是remove_reference_t<_Ty>&,也就是非const左值引用,只能绑定左值表达式,仅当传入的参数是左值时才会匹配这个重载。
它的返回值_Ty&&没有任何特殊魔法,类型完全由调用者显式传入的_Ty决定:
- 当传入的
_Ty是左值引用类型(比如int&),按引用折叠规则,_Ty&&即int& &&会折叠为int&,最终返回左值引用 - 当传入的
_Ty是非引用类型(比如int)或者右值引用类型(比如int&&),_Ty&&最终会折叠为int&&,返回右值引用
这就是它能实现双向转发的原因:两种转发结果完全由显式指定的_Ty控制,和返回值的&&写法没有直接关系。函数内部的static_cast<_Ty&&>(_Arg)也是完全按照这个规则做类型转换,把有名字的左值参数_Arg转换成对应类型的引用返回。
第二个重载仅支持转发为右值的原因
第二个重载的签名对应注释:
forward an rvalue as an rvalue
它的形参类型是remove_reference_t<_Ty>&&,即右值引用,只能绑定右值表达式,仅当传入参数是纯右值或者将亡值时才会匹配这个重载。
这个重载开头直接加了编译期拦截:
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
它直接禁止调用者给这个重载传入左值引用类型的_Ty,也就是说合法调用时,_Ty只能是非引用类型或者右值引用类型:
- 传入
_Ty=int时,_Ty&&的类型是int&& - 传入
_Ty=int&&时,按引用折叠规则,_Ty&&即int&& &&仍然是int&&
所有合法调用的返回值都是右值引用,不可能得到左值引用,因此它只支持把右值转发为右值。这个限制是必要的安全设计:右值本身代表资源可以被转移的临时对象,如果允许把右值转发为左值,很容易出现访问已经被移走资源的悬空问题,属于典型的错误用法,标准库直接在编译期阻断这种非法调用。
附两个重载的完整实现代码:
template <class _Ty> _NODISCARD constexpr _Ty&& forward( remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue return static_cast<_Ty&&>(_Arg); } template <class _Ty> _NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call"); return static_cast<_Ty&&>(_Arg); }
内容的提问来源于stack exchange,提问作者Anning Wuwang

