我熟悉移动语义和完美转发。但我有一个问题,如下:
void function(Type&& argument)
{
std::is_lvalue_reference<decltype(argument)>::value;
std::is_rvalue_reference<decltype(argument)>::value;
std::move(argument);
}
template <typename Type>
void function(Type&& argument)
{
std::is_lvalue_reference<decltype(argument)>::value;
std::is_rvalue_reference<decltype(argument)>::value;
std::forward<Type>(argument);
}
如果使用std::is_lvalue_referenceand std::is_rvalue_reference,那么很明显 C++ 知道函数内部的内容(左值或右值)。所以它std::forward做了以下事情:如果一个右值来了,它返回一个右值,如果一个左值来了,它返回一个左值。那些。有一些看似毫无意义的工作正在进行。但是,没有std::forward“完美转移”是行不通的。没有std::move“重定位语义”是行不通的。它是通过实验检查的。那么到底有什么意义呢?
考虑一个简单的例子
为了理解这里发生了什么,让我们分析两个在类型推断上下文中起作用的规则。
规则一。 推断类型时(在模板中或用于auto),类型的引用性被丢弃,即,如果类型是从参数推断的,则类型将不是引用类型,除非使用右值一起推断类型参考,但稍后会详细介绍。如果需要引用,那么使用这样的模板,使用时需要指定类型:
规则二。 在类型推断期间可以折叠(折叠)链接。这意味着如果类型推断产生“对引用的引用”,那么它就会崩溃。我将折叠规则作为真值盘:
那些。如果我们获得“对引用的引用”,那么结果类型将始终是左值引用,除非获得对右值引用的右值引用,在这种情况下,最终类型将是右值引用。
为了更清楚,让我们看一下带有 typedef 的示例:
这两种类型推理规则允许您组织可以引用任何内容的参数。
让我们再看第一个例子:
情况一:
func(v1)有v1类型double。func在参数中接受类型参数T&&(T从参数推断)。表达式v1将具有类别lvalue,因此v1不能绑定到double&&。但是,如果输出T不是 asdouble,而是 asdouble&,那么我们得到的情况double& &&是 ,这意味着链接将被折叠到double&。事实证明,T必须将类型完全推导出为lvalue-reference - double&才能成功。在返回值中,我们有T&&,这意味着推导时,我们T如何double&得到double& &&,这也折叠为double&。我们得到一个函数:很明显,在这个函数中一切都很好,没有错误。
在第二种情况下,
fucn(v2),v2是 typeconst double,所以一切都与 with 相同v1,除了类型将具有限定符const:而我们代码中的第三种情况更有趣——
func(67).67- 将给出一个临时对象类型int,即 我们最终得到一个带有右值类别的表达式。右值可以绑定到非const 右值引用,这意味着它将T输出为double。类型推断成功T可以取 asdouble,然后得到代码:那么为什么会发生错误呢?但是因为变量本身
r是一个右值引用,但它是一个命名变量,它的名字(r)的表达式将具有左值类别。如您所知,lvalue不会隐式转换为rvalue-reference ,这就是尝试返回引用r时发生错误的原因。现在让我们添加std::forward代码:为什么错误消失了?让我们考虑一个简化的设备
std::forward并编写我们自己的简单模拟。我们实现了自己的
forward,但为了简化我们丢弃constexpr,等等:将此与我们的函数进行比较:
那么有什么魔力呢?
func假设我们用v1-调用lvalue,那么我们得到它T的输出为double&,这意味着它t是一个左值引用。现在让我们看看工作forward<T&>。我们用类型实例化它T-double&。所以我们得到以下中间代码:remove_reference_t将删除链接,并在链接static_cast的返回值中和中折叠并获取函数:最终
forward给出double&。因此,传入funclvalue,我们得到:让我们
func用一个具有类别rvalue(xvalue) 的参数(表达式)来调用它——例如,std::move(v1)。我们得到Tin的类型func将显示为double,这意味着我们forward使用类型double(forward<double>(t)) 进行实例化,同时将其作为参数传递给函数rvalue-reference,我们将得到以下中间代码:最后,整个函数
func将没有错误:此代码
forward将已经返回double&&(表达式类别 - xvalue ),而不是double&(表达式类别 - lvalue )。也就是说,
forward根据模板参数和模板函数参数,它要么产生一个右值引用,整个表达式将具有xvalue category ,要么产生一个左值引用,整个表达式将具有lvalue category 。这是所有魔术结束的地方。几个简单的规则,药水就准备好了。至于
std::move- 这只是static_castrvalue -reference。它总是返回一个右值引用,并且整个表达式都有xvalue类别。