The following link provides the 4 forms of reference collapsing (if I'm correct that these are the only 4 forms): http://thbecker.net/articles/rvalue_references/section_08.html.
From the link:
A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&
Although I can make an educated guess, I would like a concise explanation for the rationale behind each of these reference-collapsing rules.
A related question, if I might: Are these reference-collapsing rules utilized in C++11 internally by such STL utilities such as std::move(), std::forward(), and the like, in typical real-world use cases? (Note: I'm specifically asking whether the reference-collapsing rules are utilized in C++11, as opposed to C++03 or earlier.)
I ask this related question because I am aware of such C++11 utilities as std::remove_reference, but I do not know if the reference-related utilities such as std::remove_reference are routinely used in C++11 to avoid need for the reference-collapsing rules, or whether they are used in conjunction with the reference-collapsing rules.
The reference collapsing rules (save for A& & -> A&, which is C++98/03) exist for one reason: to allow perfect forwarding to work.
"Perfect" forwarding means to effectively forward parameters as if the user had called the function directly (minus elision, which is broken by forwarding). There are three kinds of values the user could pass: lvalues, xvalues, and prvalues, and there are three ways that the receiving location can take a value: by value, by (possibly const) lvalue reference, and by (possibly const) rvalue reference.
Consider this function:
template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }
By value
If Call takes its parameter by value, then a copy/move must happen into that parameter. Which one depends on what the incoming value is. If the incoming value is an lvalue, then it must copy the lvalue. If the incoming value is an rvalue (which collectively are xvalues and prvalues), then it must move from it.
If you call Fwd with an lvalue, C++'s type-deduction rules mean that T will be deduced as Type&, where Type is the type of the lvalue. Obviously if the lvalue is const, it will be deduced as const Type&. The reference collapsing rules mean that Type & && becomes Type & for v, an lvalue reference. Which is exactly what we need to call Call. Calling it with an lvalue reference will force a copy, exactly as if we had called it directly.
If you call Fwd with an rvalue (ie: a Type temporary expression or certain Type&& expressions), then T will be deduced as Type. The reference collapsing rules give us Type &&, which provokes a move/copy, which is almost exactly as if we had called it directly (minus elision).
By lvalue reference
If Call takes its value by lvalue reference, then it should only be callable when the user uses lvalue parameters. If it's a const-lvalue reference, then it can be callable by anything (lvalue, xvalue, prvalue).
If you call Fwd with an lvalue, we again get Type& as the type of v. This will bind to a non-const lvalue reference. If we call it with a const lvalue, we get const Type&, which will only bind to a const lvalue reference argument in Call.
If you call Fwd with an xvalue, we again get Type&& as the type of v. This will not allow you to call a function that takes a non-const lvalue, as an xvalue cannot bind to a non-const lvalue reference. It can bind to a const lvalue reference, so if Call used a const&, we could call Fwd with an xvalue.
If you call Fwd with a prvalue, we again get Type&&, so everything works as before. You cannot pass a temporary to a function that takes a non-const lvalue, so our forwarding function will likewise choke in the attempt to do so.
By rvalue reference
If Call takes its value by rvalue reference, then it should only be callable when the user uses xvalue or rvalue parameters.
If you call Fwd with an lvalue, we get Type&. This will not bind to an rvalue reference parameter, so a compile error results. A const Type& also won't bind to an rvalue reference parameter, so it still fails. And this is exactly what would happen if we called Call directly with an lvalue.
If you call Fwd with an xvalue, we get Type&&, which works (cv-qualification still matters of course).
The same goes for using a prvalue.
std::forward
std::forward itself uses reference collapsing rules in a similar way, so as to pass incoming rvalue references as xvalues (function return values that are Type&& are xvalues) and incoming lvalue references as lvalues (returning Type&).
The rules are actually pretty simple. Rvalue reference is a reference to some temporary value that does not persist beyond the expression that uses it - in contrast to lvalue reference which references persisting data. So if you have a reference to a persisting data, no matter what other references you combine it with, the actual referenced data is an lvalue - this covers for the first 3 rules. The 4th rule is natural as well - rvalue reference to rvalue reference is still a reference to non-persistent data, hence rvalue reference is yielded.
Yes, the C++11 utilities rely on these rules, implementation provided by your link matches the real headers: http://en.cppreference.com/w/cpp/utility/forward
And yes, the collapsing rules along with template argument deduction rule are being applied when using std::move and std::forward utilities, just like explained in your link.
The usage of type traits such as remove_reference is really depends on your needs; move and forward cover for the most casual cases.
Related
I have used std::move and std::forward in C++. My question is: how are these functions actually implemented by the standard library?
If an lvalue is something you can get the address of, and an rvalue is exclusively not an lvalue, how can you actually implement these references?
Do these new facilities allow for something like:
auto x = &(3);
or something like that? Can you get a reference to an rvalue that isn't just a std::move/forward returned lvalue?
Hopefully these questions make sense. I couldn't find good information on Google, just tutorials on perfect forwarding, etc.
How is it possible to get a reference to an rvalue?
Conceptually, an rvalue expression creates a temporary object, or sometimes denotes an existing object. That can be bound to a reference like any other object; but, to avoid confusion, the language only allows that for rvalue and const lvalue references.
I have used std::move and std::forward in C++. My issue is how this is actually implemented by the compiler?
move simply returns an rvalue reference to its argument, equivalent to
static_cast<typename remove_reference<T>::type&&>(t)
The result of the function call is an rvalue (specifically, an xvalue), so it can be bound to an rvalue reference where the function argument couldn't. This allows you to explicitly move from an lvalue, using move to convert it to an rvalue, while not allowing you to accidentally move from it.
forward is similar, but overloaded to return an rvalue reference to an rvalue or rvalue reference, and an lvalue reference to anything else.
If an l-value is something you can get the address of
That's more or less correct. The official definition is that the expression "designates a function or an object", and those are things that have addresses.
and an r-value is exclusively not an l-value
Not really. Simplifying slightly, an expression is either a lvalue or an rvalue, but can be converted from one to the other. An lvalue can be implicitly converted to an rvalue; converting the other way can be done with a cast, as move does.
how can you actually implement these references?
Just like any other reference - as an alias for, or a pointer to, the object it's bound to. The only difference is which kinds of expression can be used to denote (and possibly create) the object that's bound to the reference.
Do these new facilities allow for something like auto x = &(3);
That attempts to take the address of an rvalue directly, which isn't allowed. Since the question is about references, not pointers, the following are allowed, binding a reference to a temporary object (whose lifetime is extended to match the reference):
auto && rvalue = 3;
auto const & const_lvalue = 3;
while it's not allowed to bind it to a non-const lvalue reference
auto & lvalue = 3; // ERROR
I cannot call a function: void foo(string* bar) like this: foo(&string("Hello World!")) or I get an error:
error: taking address of temporary
I also cannot call a function: void foo(string& bar) like this: foo(string("Hello World!")) or I get an error:
error: invalid initialization of non-const reference of type 'std::string& {aka std::basic_string&}' from an rvalue of type 'std::string {aka std::basic_string}'
What C++11 has provided me the ability to do is to make an rvalue reference, so I can call a function: void foo(string&& bar) like this: foo(string("Hello World!"));
Furthermore, internally to foo I can get the address of the object passed in by an rvalue reference:
void foo(string&& bar){
string* temp = &bar;
cout << *temp << " #:" << temp << endl;
}
It seems like the OP has a really good grip on rvalues. But this explanation of them was helpful to me, and may be to others. It goes into a bit of detail about why C++03 allowed constant references to rvalues, versus C++11's rvalue references.
Basically, compiler magic. The Standard describes the rules, the compiler maker just has to figure out how to implement the rules.
In practice, references are either optimized out or implemented as pointer on CPU level.
std::move isn't really special in that sense. It has a lvalue reference as input, and an rvalue reference as output. The compiler just has to apply the rvalue reference rules to the input.
Similarly, the goal of std::forward<T> is just to tell the compiler to apply a different set of rules to the argument, rules which happen to be defined so that perfect forwarding works. The function itself does nothing.
Consider this code:
template<typename T>
void foo(T&& param){ //In this case && is called universal reference
std:string tmp = std::forward<string>(param);
}
My question is if universal reference type can be deduced why do I still need to call forward ?
Why without forwarding tmp's correct c'tor won't be called even if T's type was deduced.
My second question is about reference collapsing rules:
A& && becomes A&
A&& && becomes A&&
so according this rules and taking in account universal reference why std::forward signature can't be as follows:
template<class T>
T&& forward(T&& arg){
return static_cast<T&&>(arg);
}
According to the rules from above if T's type is rvalue reference it will collapse to rvalue reference , if T's type is lvalue reference it will collapse to lvalue reference.
So why std::forward have two different signatures one for lvalue reference and one for rvalue reference am I missing something ?
My question is if universal reference type can be deduced why do I still need to call forward ?
Because as soon as you give a name to the parameter param it is an lvalue, even if the function was called with an rvalue, so it wouldn't be forwarded as an rvalue unless you use forward<T>
Why without forwarding tmp's correct c'tor won't be called even if T's type was deduced.
Because param is an lvalue. To restore the value category of the argument passed to foo you need to cast it back to string& or string&& which means you need to know the type T was deduced as, and use forward to do the cast.
So why std::forward have two different signatures one for lvalue reference and one for rvalue reference am I missing something ?
It was changed by http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3143.html
There is lots of background info in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3143.html and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html
The problem with your suggested version is that if you say forward<string> then the parameter T is not deduced so doesn't function as a forwarding reference, which means that T&& can't bind to an lvalue, and it needs to be able to bind to an lvalue in order for forward<string>(param) to work, because param is an lvalue there.
If I'm not wrong, I think that both a const reference and a rvalue reference can bind to a rvalue. Is there any practical difference between a function that returns the former and a function that returns the latter?
EDIT. I cannot modify the former, but why would I be interested in modifying a rvalue? Does it make sense?
A const lvalue reference can bind to anything. An rvalue reference can only bind to non-const rvalues.
non-const lvalue const lvalue non-const rvalue const rvalue
const T& yes yes yes yes
T&& no no yes no
As you can see, they are very different.
In addition, if a function call returns an lvalue reference, that expression is an lvalue, but if a function call returns an rvalue reference to object, that expression is an xvalue.
A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.
As for when you would want to modify an rvalue - well this is precisely what move semantics are all about. Consider the following function call:
void func(std::string);
func(std::string("Hello"));
The expression std::string("Hello") is an rvalue that creates a temporary object. When initializing the std::string parameter with this rvalue, it will choose the constructor that takes an rvalue reference - the move constructor. This constructor then steals things from the rvalue, which is typically much faster than doing a full copy. We can steal from it because we know it's temporary.
As for when you should return const lvalue references or rvalue references:
Returning a const lvalue reference is most commonly used when you want to give access to read an "internal" object (perhaps a member of a class), but not allow people to modify it.
Returning an rvalue reference is most commonly used (not common at all) when you want to allow calling code to move from an "internal" object (perhaps a member of a class). So instead of moving from a temporary returned object (as they would when returning by value), they literally move from the internal object.
This could also be achieved with a non-const lvalue reference, but then they would have to explicitly std::move it.
So it's not very likely that you'll need to return an rvalue reference.
Not that std::forward has a return type that looks like T&&. However, this is deceptive, because it may or may not be an rvalue reference depending on the type of T. See universal references.
Is there any practical difference between a function that returns the former and a function that returns the latter?
The question seems to be ill-formed. A function that returns a constant lvalue-reference provides access to an object only for reading, while a function that returns an rvalue-reference provides access for moving which means that the caller can take the contents of the referred object and move it to a different object. They are not comparable by any means.
In both cases, the references must point to an object whose lifetime spans beyond the end of the function that is returning it, as otherwise the caller will trip with undefined behavior on using that reference.
I see code on StackOverflow every once in a while, asking about some overload ambiguity with something involving a function like:
void foo(int&& param);
My question is: Why does this even come up? Or rather, when would you ever have "a reference to a reference"? How is that any different from a plain old reference? I've never run across this in real-world code, so I'm curious as to what kind of code would need this.
It's a rvalue reference, Bjarne describes it here.
Shameless copying ("quoting"):
The rvalue reference
An rvalue reference is a compound type
very similar to C++'s traditional
reference. To better distinguish these
two types, we refer to a traditional
C++ reference as an lvalue reference.
When the term reference is used, it
refers to both kinds of reference:
lvalue reference and rvalue reference.
An lvalue reference is formed by
placing an & after some type.
A a; A& a_ref1 = a; // an lvalue reference
An rvalue reference is formed by
placing an && after some type.
A a; A&& a_ref2 = a; // an rvalue reference
An rvalue reference behaves just like
an lvalue reference except that it can
bind to a temporary (an rvalue),
whereas you can not bind a (non const)
lvalue reference to an rvalue.
A& a_ref3 = A(); // Error!
A&& a_ref4 = A(); // Ok
It isn't a reference to a reference: such a thing does not exist.
It is an rvalue reference, a new feature added in C++11.
It's an rvalue reference. Note that the && token used for Boolean AND and rvalue references, and the & token used for bitwise AND and normal references, are different "words" as far as the language can tell.
An rvalue reference is (usually) bound to an object which may be left in an indeterminate state after the rvalue reference is finished, presumably because the object will then be destroyed.
Simply put, binding a variable to an rvalue reference is the usually the last thing you do to it.
Unlike a regular reference but like a const & reference, an rvalue reference can bind to an rvalue (an expression whose address cannot be directly taken). Like a regular reference but unlike a const & reference, an rvalue reference can be used to modify its object.
When programming in C++03, we can't pass an unnamed temporary T() to a function void foo(T&);. The usual solution is to give the temporary a name, and then pass it like:
T v;
foo(v);
Now, along comes C++0x - and now with rvalue references, a function defined as void foo(T&&) will allow me to pass a temporary. Which brings me to my question: since a function that takes an rvalue reference can take both rvalue references (unnamed temporaries) as well as lvalue references (named non-const references), is there any reason to use lvalue references anymore in function parameters? Shouldn't we always use rvalues as function parameters?
Granted, a function that takes an lvalue reference would prevent the caller from passing a temporary, but I'm not sure if that's a useful restriction.
"since a function that takes an rvalue reference can take both rvalue references (unnamed temporaries) as well as lvalue references (named non-const references)"
This is an incorrect statement. During the first iterations of the rvalue reference specification this was true, but it no longer is and is implemented at least in MSVC to comply with this later change. In other words, this is illegal:
void f(char&&);
char x;
f(x);
In order to call a function expecting rvalue references with an lvalue you must turn it into an rvalue like so:
f(std::move(x))
Of course, that syntax makes it quite clear what the difference between a function taking an lvalue reference and one taking an rvalue reference really is: an rvalue reference is not expected to survive the call. This is a big deal.
Now, you can of course make up a new function that does exactly what std::move does and then you "can" use rvalue references sort of like lvalue references. I thought about doing this for instance with a visitor framework I have when sometimes you simply don't care about any result of the visitor call, but other times you do and thus need an lvalue reference in those cases. With an rvalue reference I could get both...but it's such a violation of the rvalue reference semantics that I decided it was a bad idea.
Your statement may be a confusion based upon this:
template < typename T >
void f(T&&);
char x;
f(x);
This works, but not because you are passing an lvalue as an rvalue reference. It works because of reference decay (also new in C++0x). When you pass an lvalue to such a template it actually gets instantiated like so:
void f<char&>(char&&&);
Reference decay says that &&& turns into & so then the actual instantiation looks like this:
void f<char&>(char&);
In other words, you're simply passing an lvalue by reference...nothing new or special about that.
Hope that clears things up.
It's a useful restriction if that temporary must be actively disposed of, say a pointer to new memory or a limited resource like a file handle. But needing to pass those back smells more of "bad design" than it does of "useful restriction."