考虑:
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
有左值和右值的重载,为什么调用不会绑定到std::forward
的重载左值?
template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;
答案 0 :(得分:16)
它 绑定到std::forward
的重载以获取左值:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
它与T == int
绑定。指定此函数返回:
static_cast<T&&>(t)
因为T
中的f
推断为int
。所以这个重载将左值int
强制转换为xvalue:
static_cast<int&&>(t)
因此调用g(int&&)
重载。
总之,std::forward
的左值重载可能会将其参数转换为左值或右值,具体取决于调用它的T
的类型。
std::forward
的右值超载只能转换为右值。如果您尝试调用该重载并转换为左值,则程序格式错误(需要编译时错误)。
所以重载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可以将其左值参数转换为左值或右值(后者将被解释为用于重载解析目的的右值)。
重载2可以将其rvalue参数仅转换为xvalue(为了解决重载问题,它将被解释为rvalue)。
过载2适用于标有&#34; B的情况。应该将右值作为右值转发&#34;在N2951。简而言之,这种情况可以:
std::forward<T>(u.get());
您不确定u.get()
是否返回左值或左值,但如果T
不是左值引用类型,则您希望移动返回的值。但是你不能使用std::move
,因为如果T
是左值参考类型,那么你不想要从回归。
我知道这听起来有点做作。但是N2951设置了激励用例{em>激励用于std::forward
应该如何使用显式提供的模板参数的所有组合以及隐式提供的表达式类别普通参数。
这不容易阅读,但std::forward
的每个模板和普通参数组合的基本原理都在N2951。当时这对委员会来说是有争议的,而不是轻易卖出。
答案 1 :(得分:4)
为什么调用绑定到
std::forward
的带有左值的重载?
确实如此,但是std::forward
does not deduce它的模板参数,你告诉它它是什么类型,以及魔术发生的地方。您已将prval传递给f()
,因此f()
推断[T = int]
。然后它调用forward
的左值超载,由于reference collapsing,static_cast<T&&>
内的返回类型和forward
都属于int&&
类型,因此调用void g(int&&)
重载。
如果您要将左值传递给f()
int x = 0;
f(x);
f()
推导出[T = int&]
,再次调用forward
的相同左值重载,但这次返回类型和static_cast<T&&>
都是int&
,因为参考折叠规则。然后,这将调用void g(int&)
重载。
霍华德已经对你的问题有了一个很好的答案,为什么需要forward
的右值超载,但我会添加一个人为的例子来说明这两个版本在起作用。
两个重载背后的基本思想是,在传递给forward
的表达式的结果产生rvalue(prvalue或xvalue)的情况下,将调用rvalue重载。
假设您的类型foo
具有ref-qualified 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
答案 2 :(得分:2)
为了完美转发,请使用代码:
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
请注意:std :: forward需要函数参数和模板类型参数。
模板参数T将编码传递给param的参数是左值还是右值,然后转发使用它。
在这种情况下,无论是x
是什么,x
本身是左值,选择重载1, x是转发(通用)参考参数。规则如下: < / p>
f
参数&#39; s expr为左值,则x
和T
都会左值参考
输入f
参数的expr是rvalue,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 & &
等于string&
,_Tp&&
等于string& &&
string&
。(参考折叠)string
或string&&
,则为
转发函数_Tp
将为string
,__t
将为string&
_Tp&&
,string&&
将为static_assert
。std::forward<T>(u.get());
为您提供编译错误。(重载2)正如Scott Meyers所说:
鉴于std :: move和std :: forward都归结为强制转换 唯一的区别是std :: move总是施放,而 std :: forward有时只会,你可能会问我们是否可以 免除std :: move并且只使用std :: forward到处。 来自a 纯粹是技术角度,答案是肯定的:std :: forward可以做到 这一切。 std :: move是没有必要的。当然,这两个功能都不是 真的很必要,因为我们可以随处写演员,但我 希望我们同意,那将是令人讨厌的。
我不确定,应该在你真正需要它的地方使用它。如Howard Hinnant所说:
过载2适用于标有&#34; B的情况。应该将右值转发为 右值&#34;在N2951。简而言之,这种情况可以:
{{1}}
你不确定u.get()是否返回左值或右值,但是 无论哪种方式,如果T不是左值参考类型,你想要移动 返回值。但是你不能使用std :: move,因为如果T是左值 参考类型,您不想退回。
总之,它允许我们使用T&#39;类型决定是否移动。