假设我们有:
foo(A&& a);
如果你这样做
A a;
foo(a);
它不会编译并且抱怨不能将左值绑定到A&&。那很好。
但是,鉴于std :: move的签名,
template<class T> typename remove_reference<T>::type&& std::move(T&& a);
看起来它需要一个右值引用,就像在foo中一样,为什么以下代码符合?
A a;
std::move(a);
不是a
是左值?
更进一步,据说编译将实例化:
typename remove_reference<A&>::type&& std::move(A& && a);
我不明白为什么不是:
typename remove_reference<A>::type&& std::move(A && a);
我认为a
的类型为A
,而不是A&
。
答案 0 :(得分:9)
Nope move
没有采用rvalue-reference,它采用社区称为通用引用的内容。按类型推导的模板参数根据引用折叠的规则运行。这意味着:
T
为K
,那么T&&
将只是K&&
; T
为K&
,则T&&
将折叠为K&
; T
为K&&
,则T&&
将折叠为T&&
。它就像&
和&&
的逻辑AND,其中&
为0且&&
为1:
& &&
|-----------|
& | & | & |
|-----|-----|
&& | & | && |
|-----------|
这就是move
对rvalues和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&&
请注意,引用折叠仅发生在模板参数中;你不能直接输入int&& &&
并期望它编译。当然,您没有像这样手动指定类型。这些只是为了显示哪些参考文献崩溃了。
所以你真的这样称呼它:
int i;
f(i); // T is int&; int& && collapses to int&
f(4); // T is int&&; int&& && collapses to int&&
引用折叠也是move
未返回T&&
的原因:如果T
是左值引用,则引用将会崩溃,并使move
只返回左值参考。您执行remove_reference
以获取非引用类型,以便&&
真正意味着“rvalue-reference”。
您可以在此处了解详情:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
答案 1 :(得分:3)
类型演绎的语境中的句法形式T&&
(其中包括模板参数演绎,但也包括变量类型的演绎)声明为auto
)不表示右值引用,而是Scott Meyers所谓的[通用引用]。请注意,只有非常特殊的语法形式 T&&
表示通用引用,而其他类似形式则不被视为通用引用。例如:
template<typename T>
void foo(T&& t); <-- T&& is a universal reference
template<typename T>
void foo(T const&& t); <-- T const&& is NOT a universal reference
template<typename T>
void foo(S<T>&& t); <-- S<T>&& is NOT a universal reference
template<typename T>
struct S { void foo(T&& t); }; <-- T&& is NOT a universal reference
通用引用可以绑定到左值和右值。如果绑定了A
类型的左值,则T
被推断为A&
,并且由于规则,A&
的参数类型将解析为A& &&
(左值引用) 引用折叠(A&
变为A
)。如果绑定了T
类型的右值,则A
推导为A&&
,并且参数的类型将解析为T&& &
(右值参考)。
[注意:参考折叠规则可能看起来很复杂,但它们实际上非常简单:引用Stephan T. Lavavej,“左值引用具有传染性”,这意味着当形式{ {1}},T& &
或T& &&
获得实例化,它们始终会解析为T&
- 只有T&& &&
形式被解析为T&&
]
这就是当参数是左值时std::move
函数模板将被实例化的原因(T
推导为T&
):< / p>
typename remove_reference<A&>::type&& std::move(A& && a);
虽然当参数是右值(T
推断为A
时),它将被如下实例化
typename remove_reference<A>::type&& std::move(A&& a);
答案 2 :(得分:1)
尽管其他人已经说过,但标准只讨论了右值参考。
std :: move的工作原理的关键是模板参数推导规则中的一个明确的特殊规则:
[...]如果[声明的函数参数类型]是右值引用 到cv-unqualified模板参数,参数是左值, 类型“左值引用A”用于代替A表示类型 扣。[...]
另一部分是参考折叠的规则,即
如果[...]类型模板参数[...]表示作为参考的类型TR 类型T,尝试创建类型“对cv TR的左值引用” 创建类型“左值引用T”,同时尝试创建 类型“对cv TR的rvalue引用”创建类型TR。
现在在template<class T> typename remove_reference<T>::type&& std::move(T&& a);
函数参数a匹配上面的规则(“rvalue reference to cv-unqualified template parameter”),因此推导出的类型将是对参数类型的左值引用,如果参数是左值。在你的情况下导致T = A&amp;。
将其代入移动收益的声明
remove_reference<A&>::type&& std::move<A&>(A& && a);
使用remove_reference的定义和参考折叠规则(对TR =&TR; TR的rvalue引用),使得:
A&& std::move<A&>(A& a);
Scott Meyer的通用引用概念,正如其他答案中提出的那样,是一种有用的方法来记住类型推导和参考折叠规则组合的这种令人惊讶的效果:rvalue引用a推导类型可能最终成为左值引用(如果类型可能被推断为左值引用)。但是标准中没有通用引用。正如斯科特迈尔斯所说:这是谎言 - 但谎言比真理更有帮助......
请注意,std :: forward是这个主题的一个不同的转折:它使用额外的间接来防止参数推断(因此必须明确给出类型),但也使用引用折叠来将左值转发为左值和右值作为lvalues和rvalues右值。