如何推导出std :: move模板参数?

9 c++ templates c++11

假设我们有:

foo(A&& a);
Run Code Online (Sandbox Code Playgroud)

如果你这样做

A a;
foo(a);
Run Code Online (Sandbox Code Playgroud)

它不会编译,抱怨不能将左值绑定到A &&.那很好.

但是,鉴于std :: move的签名,

template<class T> typename remove_reference<T>::type&& std::move(T&& a);
Run Code Online (Sandbox Code Playgroud)

看起来它需要一个右值引用,就像在foo中一样,为什么下面的代码符合?

A a;
std::move(a);
Run Code Online (Sandbox Code Playgroud)

是不是a一个左值?

furthur,据说编译将实例化:

typename remove_reference<A&>::type&& std::move(A& && a);
Run Code Online (Sandbox Code Playgroud)

我不明白为什么不是:

typename remove_reference<A>::type&& std::move(A && a);
Run Code Online (Sandbox Code Playgroud)

它看起来像是a类型A,而不是A&.

Set*_*gie 10

Nope move没有采用rvalue-reference,它采用了被社区称为通用引用的东西.按类型推导的模板参数根据参考折叠规则进行操作.这意味着:

  • 如果TK,那么T&&就是K&&;
  • 如果TK&,那么T&&将崩溃到K&;
  • 如果TK&&,那么T&&将崩溃到T&&.

它就像是一个逻辑 - &并且&&其中&的0是0并且&&是1:

      &    &&
   |-----------|
&  |  &  |  &  |
   |-----|-----|
&& |  &  | &&  |
   |-----------|
Run Code Online (Sandbox Code Playgroud)

这就是movervalues和lvalues的工作原理.

例子:

template<typename T>
void f(T&&);

f<int>   // T is int;   plugging int into T makes int&& which is just int&&
f<int&>  // T is int&;  plugging int& into T is int& && which collapse to int&
f<int&&> // T is int&&; plugging int&& into T is int&& && which collapse to int&&
Run Code Online (Sandbox Code Playgroud)

请注意,引用折叠仅发生在模板参数中; 你不能直接输入int&& &&并期望它编译.当然,您没有像这样手动指定类型.这些仅仅是为了显示哪些引用崩溃了.

所以你真的这样称呼它:

int i;

f(i); // T is int&;  int& && collapses to int&
f(4); // T is int&&; int&& && collapses to int&&
Run Code Online (Sandbox Code Playgroud)

参考折叠也是为什么move不返回a T&&:如果T是左值引用则引用将崩溃并且move仅返回左值引用.你remove_reference可以使用非引用类型,这样&&就意味着"rvalue-reference".

您可以在此处了解更多信息:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers


Joe*_*rgB 1

不管其他人怎么说,该标准只讨论右值引用。

\n\n

std::move 的工作原理的关键是模板参数推导规则中的显式特殊规则:

\n\n
\n

[...] 如果 [声明的函数参数类型] 是对 cv 未限定模板参数的右值引用,并且参数是左值,则类型 \xe2\x80\x9cl 对 A\xe2\x80 的值引用\x9d 用于代替 A 进行类型\n 推导。[...]

\n
\n\n

另一部分是参考折叠的规则,其中说

\n\n
\n

如果类型模板参数 [...] 表示类型 TR 是对类型 T 的引用,则尝试创建对 cv TR\xe2\x80 的类型 \xe2\x80\x9cl 值引用\x9d\n 创建对 T\xe2\x80\x9d 的类型 \xe2\x80\x9clvalue 引用,而尝试创建\n 对 cv TR\xe2\x80\x9d 的类型 \xe2\x80\x9crvalue 引用会创建类型 TR。

\n
\n\n

现在在template<class T> typename remove_reference<T>::type&& std::move(T&& a);函数参数中, a 匹配上述规则(“对 cv 不合格模板参数的右值引用”),因此如果参数是左值,则推导的类型将是对参数类型的左值引用。在你的情况下,这导致 T = A& 。

\n\n

将其代入移动声明中得到

\n\n
remove_reference<A&>::type&& std::move<A&>(A& && a);\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用remove_reference的定义和引用折叠规则(对TR => TR的右值引用),使得:

\n\n
A&& std::move<A&>(A& a);\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如在其他答案中提出的那样, Scott Meyer 的通用引用概念是记住类型推导规则和引用折叠规则组合的这种令人惊讶的效果的有用方法:对推导类型的右值引用可能最终会成为左值引用(如果该类型可以推断为左值引用)。但没有通用的参考但该标准中正如斯科特·迈耶斯所说:这是一个谎言,但这个谎言比真相更有帮助……

\n\n

请注意,std::forward 是此主题的不同转折:它使用额外的间接来防止参数推导(以便必须显式给出类型),但也使用引用折叠将左值转发为左值,将右值转发为右值。

\n