
[英]How does std::apply forward parameters without explicit std::forward?
[英]How does std::forward receive the correct argument?
考虑:
void g(int&);
void g(int&&);
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
int main()
{
f(10);
}
由于id-expression x
是一个左值,并且std::forward
具有lvalues和rvalues的重载,为什么调用不会绑定到带有左值的std::forward
的重载?
template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;
它确实绑定std::forward
一个左值的std::forward
的重载:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
它与T == int
绑定。 指定此函数返回:
static_cast<T&&>(t)
因为f
的T
推导为int
。 所以这个重载将lvalue int
强制转换为xvalue:
static_cast<int&&>(t)
因此调用g(int&&)
重载。
总之, std::forward
的左值重载可以将其参数转换为lvalue或rvalue,具体取决于调用它的T
的类型。
std::forward
的rvalue重载只能转换为rvalue。 如果您尝试调用该重载并转换为左值,则程序格式错误(需要编译时错误)。
超载1:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
捕获左值。
超载2:
template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;
捕获rvalues(xvalues和prvalues)。
重载1可以将其lvalue参数转换为lvalue或xvalue(后者将被解释为用于重载解析目的的rvalue)。
Overload 2可以将其rvalue参数仅转换为xvalue(为了解决重载问题,它将被解释为rvalue)。
过载2用于N2951中标记为“B.应将右值作为右值转发”的情况 。 简而言之,这种情况可以:
std::forward<T>(u.get());
你不确定u.get()
返回左值或右值,但是如果T
不是左值引用类型,你想要移动返回的值。 但你不使用std::move
,因为如果T
是一个左值引用类型,你不想从返回移动。
我知道这听起来有点做作。 然而, N2951在设置激励用例方面遇到了很大的麻烦,因为std::forward
应该使用显式提供的模板参数的所有组合以及普通参数的隐式提供的表达式类别来表现。
这不是一个简单的阅读,但模板和普通参数的每个组合对std::forward
基本原理是在N2951中 。 当时这对委员会来说是有争议的,而不是轻易卖出。
为什么调用没有绑定到带有左值的
std::forward
的重载?
它确实如此,但是std::forward
并没有推断出它的模板参数,你告诉它它是什么类型,这就是魔术发生的地方。 你将prvalue传递给f()
所以f()
推导出[T = int]
。 然后它调用forward
的lvalue重载,并且由于引用折叠 ,在forward
中发生的返回类型和static_cast<T&&>
将是int&&
类型,因此调用void g(int&&)
重载。
如果你要将左值传递给f()
int x = 0;
f(x);
f()
推导出[T = int&]
,再次调用forward
lvalue重载,但这次返回类型和static_cast<T&&>
都是int&
,同样是因为参考折叠规则。 然后,这将调用void g(int&)
重载。
霍华德已经对你的问题有了一个很好的答案,为什么需要rvalue forward
,但我会添加一个人为的例子来说明这两个版本在起作用。
两个重载背后的基本思想是,在传递给forward
的表达式的结果产生rvalue(prvalue或xvalue)的情况下,将调用rvalue重载。
假设你有一个类型foo
,它有一对ref限定的get()
成员函数重载。 带有&&
限定符的那个返回一个int
而另一个返回int&
。
struct foo
{
int i = 42;
int get() && { return i; }
int& get() & { return i; }
};
并且假设f()
在传递给它的任何内容上调用get()
成员函数,并将其转发到g()
template<class T>
auto f(T&& t)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get()));
}
foo foo1;
f(foo1); // calls lvalue overload of forward for both calls to forward
f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t)
// but calls rvalue overload for outer call to forward
为了完美转发,作为您的代码:
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
请注意:std :: forward需要函数参数和模板类型参数。 模板参数T将编码传递给param的参数是左值还是右值 ,然后转发使用它。 在这种情况下,无论x
是什么, x
本身是左值,选择重载1, x是转发(通用)参考参数。规则如下:
f
参数的expr是左值,则x
和T
都将值左右引用类型 f
参数的expr是右值, T
将是非引用类型。 例如使用gcc源代码:
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); } //overload 1
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);
}// overload 2
string
或string&
,对于转发函数_Tp
将是string&
, __t
将是string & &
_Tp&&
等于string&
, _Tp&&
将是string& &&
_Tp&&
等于string&
。(引用折叠) string
或string&&
,对于forward函数_Tp
将是string
, __t
将是string&
, _Tp&&
将是string&&
。 static_assert
给你一个编译错误。(重载2) 正如Scott Meyers所说:
鉴于std :: move和std :: forward都归结为强制转换,唯一的区别是std :: move总是强制转换,而std :: forward有时候只会这样做,你可能会问我们是否可以省去std :: move并且只是在任何地方使用std :: forward。 从纯粹的技术角度来看,答案是肯定的:std :: forward可以做到这一切。 std :: move不是必需的。 当然,这两种功能都不是必需的,因为我们可以在任何地方写出演员,但我希望我们同意那将是,令人讨厌的。
我不确定,应该在你真正需要的地方使用它。如Howard Hinnant所说:
过载2用于N2951中标记为“B.应将右值作为右值转发”的情况。 简而言之,这种情况可以:
std::forward<T>(u.get());
你不确定u.get()是否返回左值或右值,但是如果T不是左值引用类型,你想要移动返回的值。 但是你不使用std :: move,因为如果T是左值引用类型,你不想从返回移动。
总之,它允许我们使用T的类型来决定是否移动。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.