When a lvalue is passed to T&&, what will happen? - c++

Here is an exercise from C++ Primer 5th Edition:
Exercise 16.45: Given the following template, explain what happens if
we call g on a literal value such as 42. What if we call g on a
variable of type int? P.690
template <typename T>
void g(T&& val)
{
std::vector<T> v;
}
int main()
{
//g(42);
int i;
g(i);
}
When calling on 42 , it compiled.
When on i, the compiler complained a lot of errors, part of which is pasted as below.
forming pointer to reference type 'int&'
My questions are
When calling on literal value ,42 in this case, what type was deduced for T?
when on i, why didn't it compile? How to understand these error messages?

From http://thbecker.net/articles/rvalue_references/section_08.html
The first of the remaining two rules for rvalue references affects old-style lvalue references as well. Recall that in pre-11 C++, it was not allowed to take a reference to a reference: something like A& & would cause a compile error. C++11, by contrast, introduces the following reference collapsing rules:
A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&
Secondly, there is a special template argument deduction rule for function templates that take an argument by rvalue reference to a template argument:
template<typename T>
void foo(T&&);
Here, the following apply:
When foo is called on an lvalue of type A, then T resolves to A& and hence, by the reference collapsing rules above, the argument type effectively becomes A&.
When foo is called on an rvalue of type A, then T resolves to A, and hence the argument type becomes A&&.
So case 1, when passing 42, you are calling g with a rvalue, so T is resolved to int thus g's parameter is int&& and std::vector is legal.
In case 2, when passing i, you are calling g with a lvalue, so T is resolved to int& thus g's parameter is int& and std::vector<int&> is NOT legal.
Remove the line with the vector and it will work fine in both cases.

Related

Universal reference l-value not copying object

Why these asserts work for the below code? The universal reference should bind to l-value reference run(T& a), and copy an object b from a. However both objects addresses "a" and "b" are the same in the run() function. Tested with C++11/14/17/2a gcc-9.2 and clang++-6.0. What part of the standard says this is valid? did not find anything related.
#include <cassert>
#include <utility>
template <typename T>
void run(T&& a)
{
T b {std::forward<T>(a)};
++b;
assert(b == a);
assert(&a == &b);
}
int main()
{
int value {10};
run(value); // asserts work, not expected
// run(std::move(value)); // the asserts don't work as expected
}
However both objects addresses "a" and "b" are the same in the run() function.
When being passed an lvalue, T is deduced as lvalue-reference, i.e. int&. (int& && collapses to int&, so the type of function parameter a is int&.) Then b is declared as a reference binding to a.
When being passed an rvalue, T is deduced as int. (So the type of function parameter a is int&&.) Then b is declared as an independent variable copied from a.
In run(value), value is an lvalue, and it needs to match with T&&. Lvalues cannot bind to rvalue references, so T = int and T = int&& won’t do, as then T&& = int&&. The only thing that works is T = int&. Due to reference collapsing, an rvalue reference to lvalue reference is an lvalue reference, so the instantiation of run looks like:
template<>
void run<int&>(int &a) {
int &b{a}; // expanding std::forward
++b;
assert(b == a);
assert(&b == &a);
}
Obviously, the assertions always pass. Now, for run(std::move(value)), the argument is indeed an rvalue, and you get T = int. Then
template<>
void run<int>(int &&a) {
int b{std::move(a)};
++b;
assert(b == a);
assert(&b == &a);
}
This of course fails. Perhaps you should replace
T b{std::forward<T>(a)};
with
std::decay_t<T> b{std::forward<T>(a)};
This will remove references from T (ensuring b is a new (copied/moved) object) and also handle arrays and functions (by making b a pointer even if a isn’t).
Doubt you need them, but [temp.deduct.call]/3 talks about the template deduction of forwarding references, and [dcl.init.list]/3.9 says that list-initializing a reference just binds it to the element of initializer list. Also [forward], well, explains std::forward<T>. Basically, if T is an lvalue reference, then std::forward<T>(x) is an lvalue, and otherwise an xvalue (a kind of rvalue). (Basically it’s a conditional std::move.)

rvalue reference matching (perfect forwarding example)

I got confused by the following perfect forwarding function, where the template parameter T can match rvalue or lvalue references:
template<typename T>
void foo(T&& t){
T::A; // intended error to inspect type
}
int main(){
std::vector<int> a;
std::vector<int> && b = std::move(a);
foo(b); // T is std::vector<int> &
foo(std::move(a)); // T is std::vector<int>
}
I dont understand why the template argument deduction of T in foo is so different in these two cases? Whats the fundamental difference and important what is t's type in function foo.
std::move(a) returns a rvalue reference and b is already a rvalue reference (but has a name).
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Can anyone shine some light into this :-)
There is a special type deduction rule when && is used with templates.
template <class T>
void func(T&& t) {
}
"When && appears in a type-deducing context, T&& acquires a special
meaning. When func is instantiated, T depends on whether the argument
passed to func is an lvalue or an rvalue. If it's an lvalue of type U,
T is deduced to U&. If it's an rvalue, T is deduced to U:"
func(4); // 4 is an rvalue: T deduced to int
double d = 3.14;
func(d); // d is an lvalue; T deduced to double&
float f() {...}
func(f()); // f() is an rvalue; T deduced to float
int bar(int i) {
func(i); // i is an lvalue; T deduced to int&
}
Also, reference collapsing rule is a good read.
Check this out for a really good explanation:
perfect forwarding
If you think about the signature of your function, the type of the parameter is T&&. In your second example, T is deduced to vector<int>, that means that the type of the parameter to your function is vector<int>&&. So you are still passing by (rvalue) reference.
In the other case, you deduce T to vector<int>&. So the type of the argument is vector<int> & &&... or it would be, but references to references are not allowed. Reference collapsing takes over, and any double reference involving an lvalue reference become an lvalue reference. So you are passing by lvalue reference.
As far as b goes, this is a well known gotcha of rvalue references. Essentially, b's type is rvalue reference, but b itself still has a value category of lvalue. Think of it this way: b itself is a variable, that must live on the stack somewhere, and have an address. So it's an lvalue. This is precisely way calling std::forward when forwarding arguments is necessary. If you didn't do it, then they would always be forwarded as lvalue arguments.
I really recommend this Scott Meyers article: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers. Read it carefully!
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Yes, that's exactly it. It makes more sense if you think about rvalue reference function parameters: the caller is specifying that the function can do whatever it wants with the objects it gets. So from inside the function body, in order to make sure the code really can do whatever it wants with it, the parameter should be treated as an lvalue. That same argument can also be made for other rvalue references, including the b in your example, albeit to a lesser extent.
The expressions a and b are both lvalues, and the expression std::move(a) is an rvalue.
The deduction for the parameter T makes use of special reference collapsing rules so that the type of t is either an lvalue or an rvalue reference as needed to bind to the function call argument.

How are std::move template parameters deduced?

Suppose we have:
foo(A&& a);
if you do
A a;
foo(a);
it won't compile and complain cannot bind a lvalue to A&&. that's perfectly fine.
However, given the signature of std::move,
template<class T> typename remove_reference<T>::type&& std::move(T&& a);
Looks like it takes a rvalue reference, just as in foo, why the following code complies?
A a;
std::move(a);
isn't a is a lvalue?
furthur, it is said the compile will instantiate:
typename remove_reference<A&>::type&& std::move(A& && a);
I don't understand why it is not:
typename remove_reference<A>::type&& std::move(A && a);
it looks to me a is of type A, not A&.
Nope move doesn't take an rvalue-reference, it takes what has been dubbed a universal reference by the community. Template parameters being type-deduced behave according to the rules of reference collapsing. This means:
if T is K, then T&& will simply be K&&;
if T is K&, then T&& will collapse to K&;
if T is K&&, then T&& will collapse to T&&.
It's like a logical-AND of the & and the && where & is 0 and && is 1:
& &&
|-----------|
& | & | & |
|-----|-----|
&& | & | && |
|-----------|
And that's 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&&
Note that reference collapsing only happens with template parameters; you can't directly type int&& && and expect it to compile. Of course, you don't specify types manually like that. Those are just to show what references collapse to.
So you'd really call it like this:
int i;
f(i); // T is int&; int& && collapses to int&
f(4); // T is int&&; int&& && collapses to int&&
Reference collapsing is also the reason why move doesn't return a T&&: the references would collapse if T were an lvalue reference and make move just return an lvalue reference. You do remove_reference to get to a non-reference type so that the && will really mean "rvalue-reference".
You can learn more here: http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
The syntactic form T&& in the context of type deduction (which includes template argument deduction, but for instance also the deduction of the type of a variable declared as auto) does not indicate an rvalue reference, but rather what Scott Meyers calls a [universal reference]. Please notice, that only the very particular syntactic form T&& denotes a universal reference, while other, similar forms are not regarded as such. For instance:
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 to lvalues and to rvalues. If an lvalue of type A is bound, then T is deduced to be A& and the type of the argument resolves into A& (lvalue reference) due to the rule of reference collapsing (A& && becomes A&). If an rvalue of type A is bound, then T is deduced to be A and the type of the argument resolves into A&& (rvalue reference).
[Note: Reference collapsing rule might seem complicated, but they are actually quite easy: to quote Stephan T. Lavavej, "lvalue references are contagious", meaning that when the forms T&& &, T& &, or T& && get instantiated, they always resolve into T& - only the form T&& && is resolved into T&&]
This is why the std::move function template will be instantiated as follows when the argument is an lvalue (T is deduced to be T&):
typename remove_reference<A&>::type&& std::move(A& && a);
while it will be instantiated as follows when the argument is an rvalue (T is deduced to be A)
typename remove_reference<A>::type&& std::move(A&& a);
Despite what others have said, the standard only talks about rvalue references.
The key to how this works for std::move is an explicit special rule in the rules for template argument deduction:
[...] If [the declared function parameter type] is an rvalue reference
to a cv-unqualified template parameter and the argument is an lvalue,
the type “lvalue reference to A” is used in place of A for type
deduction.[...]
The other part are the rules for reference collapsing, which say that
If [...] a type template-parameter [...] denotes a type TR that is a reference to
a type T, an attempt to create the type “lvalue reference to cv TR”
creates the type “lvalue reference to T”, while an attempt to create
the type “rvalue reference to cv TR” creates the type TR.
Now in template<class T> typename remove_reference<T>::type&& std::move(T&& a); the function parameter a matches above rule ("rvalue reference to cv-unqualified template parameter"), so the deduced type will be an lvalue reference to the argument type, if the argument is an lvalue. In your case that leads to T = A&.
Substituting that into the declaration of move yields
remove_reference<A&>::type&& std::move<A&>(A& && a);
Using the definition of remove_reference and the reference collapsing rule (rvalue reference to TR => TR), makes this:
A&& std::move<A&>(A& a);
Scott Meyer's universal reference concept, as put forward in other answers, is a helpful way to remember this surprising effect of the combination of the rules for type deduction and of reference collapsing: rvalue references to a deduced type may end up being lvalue references (if the type may be deduced to be a lvalue reference). But there are no universal references int the standard. As Scott Meyers says: it is a lie - but a lie that is more helpful than the truth...
Note that std::forward is a different twist on this theme: it uses an extra indirection to prevent argument deduction (so that the type must be given explicitly), but also uses reference collapsing to forward lvalues as lvalues and rvalues as rvalues.

Are unnamed objects and temporary objects equivalent?

In my effort to understand rvalue references, I have been pondering when the compiler will determine that a particular function argument is an rvalue reference, and when it will determine it to be an lvalue reference.
(This issue is related to reference collapsing; see Concise explanation of reference collapsing rules requested: (1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& , and (4) A&& && -> A&&).
In particular, I have been considering if the compiler will always treat unnamed objects as rvalue references and/or if the compiler will always treat temporary objects as rvalue references.
In turn, this leads me to question whether unnamed objects are equivalent to temporary objects.
My question is: Are unnamed objects always temporary; and are temporary objects always unnamed?
In other words: Are unnamed objects and temporary objects equivalent?
I might be wrong, since I'm not sure what the definition of "unnamed object" is. But consider the argument of the foo() function below:
void foo(int)
{ /* ... */ }
int main()
{ foo(5); }
foo()'s argument is unnamed, but it's not a temporary. Therefore, unnamed objects and temporary objects are not equivalent.
Temporary objects can be named.
Very common case - when passed as a parameter to a function.
Another less common case - binding a const reference to an rvalue result of a function.
int f(int i) { return i + 1; }
int g() { const int &j = f(1); return j; }
Unnamed objects are often temporary, but not always. For example - anonymous union object:
struct S
{
union { int x; char y; };
} s;
And, of course, any object created by operator new.
Perhaps there are other cases, but even only these can serve as counterexamples to the hypothesis :)
I have been pondering when the compiler will determine that a particular function argument is an rvalue reference, and when it will determine it to be an lvalue reference.
I assume you are talking about function templates with universal reference parameters, like this?
template<typename T>
void foo(T&& t)
{
}
The rules are very simple. If the argument is an rvalue of type X, then T will be deduced to be X, hence T&& means X&&. If the argument is an lvalue of type X, then T will be deduced to be X&, hence T&& means X& &&, which is collapsed into X&.
If you were really asking about arguments, then the question does not make much sense, because arguments are never lvalue references or rvalue references, because an expression of type X& is immediately converted to an expression of type X, which denotes the referenced object.
But if you actually meant "How does the compiler distinguish lvalue arguments from rvalue arguments?" (note the missing reference), then the answer is simple: the compiler knows the value category of every expression, because the standard specifies for every conceivable expression what its value category is. For example, the call of a function is an expression that can belong to one of three value categories:
X foo(); // the expression foo() is a prvalue
X& bar(); // the expression bar() is an lvalue
X&& baz(); // the expression baz() is an xvalue
(Provided, of course, that X itself is not a reference type.)
If none of this answers your question, please clarify the question. Also, somewhat relevant FAQ answer.

rvalue template argument implicitly used as lvalue, and std::forwarding working

This example on the usage of std::forward is puzzling me. This is my edited version:
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
struct A{
A(int&& n) { cout << "rvalue overload, n=" << n << "\n"; }
A(int& n) { cout << "lvalue overload, n=" << n << "\n"; }
};
template<typename> void template_type_dumper();
template<class T, class U>
unique_ptr<T> make_unique(U&& u){
//Have a "fingerprint" of what function is being called
static int dummyvar;
cout<<"address of make_unique::dummyvar: "<<&dummyvar<<endl;
//g++ dumps two warnings here, which reveal what exact type is passed as template parameter
template_type_dumper<decltype(u)>;
template_type_dumper<U>;
return unique_ptr<T>(new T(forward<U>(u)));
}
int main()
{
unique_ptr<A> p1 = make_unique<A>(2); // rvalue
int i = 1;
unique_ptr<A> p2 = make_unique<A>(i); // lvalue
}
The output is
address of make_unique::dummyvar: 0x6021a4
rvalue overload, n=2
address of make_unique::dummyvar: 0x6021a8
lvalue overload, n=1
and the warnings about reference to template_type_dumper show that in the first instantiation, decltype(u) = int&& and U = int, for the second decltype(u) = int& and U = int&.
It's evident that there are two different instantiations as expected, but her are my questions:
how can std::forward work here? In the first instantiation, its template argument is explicitly U = int, how can it know that it has to return a rvalue-reference? What would happen if I specified U&& instead?
make_unique is declared to take a rvalue-reference. How come u can be a lvalue-reference? Is there any special rule that I am missing?
make_unique is declared to take a rvalue-reference. How come u can be a lvalue-reference? Is there any special rule that I am missing?
make_unique is declared to take a reference. What kind that reference is is to be deduced. If an lvalue of type foo is passed, U is deduced as foo& and U&& becomes foo& because of the reference collapsing rules (basically, "combining" an lvalue reference with another reference always produces an lvalue reference; combining two rvalue references produces an rvalue reference). If an rvalue of type foo is passed, U is deduced as foo and U&& is foo&&.
This is one of the things that powers perfect forwarding: with U&& you can take both lvalues and rvalues, and U is deduced to match the appropriate value category. Then with std::forward you can forward the values preserving that same value category: in the first case, you get std::forward<foo&> which forwards an lvalue, and in the second one, you get std::forward<foo> which forwards an rvalue.
In the first instantiation, its template argument is explicitly U = int, how can it know that it has to return a rvalue-reference?
Because the return type of std::forward<T> is always T&&. If you pass int it returns int&&. If you pass int& it returns int& again because of the reference collapsing rules.
What would happen if I specified U&& instead?
You would have std::forward<int&&> and the reference collapsing rules make int&& && an rvalue reference still: int&&.