How are the std :: move parameters of the template displayed?

Let's pretend that:

foo(A&& a); 

if you do

 A a; foo(a); 

it will not compile and complain cannot tie the lvalue to A & &. it's fine.

However, given the signature std :: move,

 template<class T> typename remove_reference<T>::type&& std::move(T&& a); 

It looks like an rvalue reference is required, as in foo, why does the following code match?

 A a; std::move(a); 

is not a lvalue?

furthur, said that compilation will create an instance:

 typename remove_reference<A&>::type&& std::move(A& && a); 

I do not understand why this is not so:

 typename remove_reference<A>::type&& std::move(A && a); 

it seems to me that a is of type a , not A& .

+7
source share
3 answers

Despite what others have said, the standard only talks about rvalue links.

The key to how this works for std :: move is an explicit special rule in the rules for outputting a template argument:

[...] If [the type parameter of the declared function] is a rvalue reference to a cv-unqualified template parameter, and the argument is an lvalue, the type "lvalue reference to A" is used instead of A for the residue type. [...]

The other part is the rules for link folding, which say that

If the template type [...] type type [...] denotes a type TR, which is a reference to type T, an attempt to create a type of "lvalue reference to cv TR" creates a type of "lvalue reference to T", while an attempt to create type "rvalue reference to cv TR" creates type TR.

Now in template<class T> typename remove_reference<T>::type&& std::move(T&& a); the function parameter a matches the rule above ("rvalue reference to cv-unqualified template parameter"), so the type being deduced will be an lvalue reference to the argument type if the argument is an lvalue. In your case, this results in T = A &.

Substituting this into the declaration of the results of the move

 remove_reference<A&>::type&& std::move<A&>(A& && a); 

Using the definition of remove_reference and the rule for dropping the link (rvalue reference to TR => TR), does the following:

 A&& std::move<A&>(A& a); 

Scott Meyer's universal reference concept, set out in other answers, is a useful way to remember this amazing effect of combining rules for type subtraction and referential collapse: rvalue references to an inferred type may turn out to be an lvalue of the reference (if the type can be evaluated as an lvalue reference). But there are no universal references in the standard. As Scott Meyers says: this is a lie - but a lie that is more useful than the truth ...

Note that std :: forward is another twist on this topic: it uses additional indirect directives to prevent the argument from being subtracted (so the type must be specified explicitly), but it also uses a link that compresses lvalues ​​as lvalues ​​to be forwarded. and rvalues ​​as rvalues.

+1
source

Nope move does not accept rvalue-reference, it takes what has been called a universal community reference. Type parameters displayed by type behave in accordance with the rules of reference folding. It means:

  • if T K , then T&& will be just K&& ;
  • if T K& , then T&& will collapse to K& ;
  • if T - K&& , then T&& will collapse to T&& .

It looks like logical-AND from & and && , where & is 0, and && is 1:

  & && |-----------| & | & | & | |-----|-----| && | & | && | |-----------| 

And how move works for both rvalues ​​and lvalues.

Examples:

 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&& 

Please note that link folding occurs only with template parameters; you cannot directly type int&& && and expect it to compile. Of course, you do not specify types manually. This is just to show what the links are accessing.

So you really call it that:

 int i; f(i); // T is int&; int& && collapses to int& f(4); // T is int&&; int&& && collapses to int&& 

Resetting links is also the reason that move does not return T&& : links will be reset if T was a link to an lvalue and to make move just return an lvalue link. You execute remove_reference to switch to a non-reference type so that && really means "rvalue-reference".

You can find out more here: http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

+8
source

The syntactic form of T&& in the context of type inference (including the output of a template argument, but, for example, also the type inference of a variable declared as auto ) does not indicate a rvalue reference, but rather Scott Meyers calls [universal reference]. Note that only the special syntax form T&& denotes a universal link, while other similar forms are not considered as such. For example:

 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 

Universal references can bind both lvalues ​​and r. If an A type lvalue is bound, then T is inferred as A& , and the argument type resolves to A& (value reference) due to the folding of the reference rule ( A& && becomes A& ). If an rvalue of type A bound, then T is inferred as A , and the argument type is resolved to A&& (rvalue reference).

[ Note : the link collapse rule may seem complicated, but it's actually quite simple: to quote Stephan T. Lavavej, β€œlink links are contagious,” meaning that when forms T&& & T& & or T& && get an instance, they always allowed on T& - only form T&& && permitted on T&& ]

This is why the std::move function std::move will be created as follows when the argument lvalue ( T is output as T& ):

 typename remove_reference<A&>::type&& std::move(A& && a); 

while it will be created as follows if the argument is rvalue ( T is output as A )

 typename remove_reference<A>::type&& std::move(A&& a); 
+3
source