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.)
Related
Trying to understand std::move, I found this answer to another question.
Say I have this function
Object&& move(Object&& arg)
{
return static_cast<Object&&>(arg);
}
What I think I understand:
arg is an lvalue (value category).
arg is of type "rvalue ref to Object".
static_cast converts types.
arg and the return type both being of type "rvalue ref to Object", the static_cast is unnecessary.
However, the linked answer says:
Now, you might wonder: do we even need the cast? The answer is: yes, we do. The reason is simple; named rvalue reference is treated as lvalue (and implicit conversion from lvalue to rvalue reference is forbidden by standard).
I still don't understand why the static_cast is necessary given what I said above.
the static_cast is unnecessary.
It may seem so, but it is necessary. You can find out easily by attempting to write such function without the cast, as the compiler should tell you that the program is ill-formed. The function (template) returns an rvalue reference. That rvalue reference cannot be bound to an lvalue. The id-expression arg is an lvalue (as you stated) and hence the returned rvalue reference cannot be bound to it.
It might be easier to understand outside of return value context. The rules are same here:
T obj;
T&& rref0 = obj; // lvalue; not OK
T&& rref1 = static_cast<T&&>(obj); // xvalue; OK
T&& rref2 = rref1; // lvalue; not OK
T&& rref3 = static_cast<T&&>(rref1); // xvalue; OK
I have the following mental model for it (let's use int instead of Object).
Objects which have a name are "sitting on the ground". They are lvalues; you cannot convert them to rvalue references.
int do_stuff(int x, int&& y) {...} // both x and y have a name
When you do calculations, you pick objects from the ground, do your stuff in mid-air and put the result back.
x + y; // it's in mid-air
do_stuff(4, 5); // return value is in mid-air
These temporary results can be converted to rvalue references. But as soon as you "put them onto the ground", they behave as lvalues.
int&& z = x + y; // on the ground
int&& z = do_stuff(6, 7); // on the ground
I am sure it only helps in simple situations, but at least it gives some real-world analogy to how C++ works.
Your first bullet is incorrect fundamentally, arg is not a lvalue, neither it is an rvalue. Neither it's a rvalue or lvalue reference, because std::move is a template. In template context a function argument of type T&& is a forwarding reference, if T is a template-parameter. Forwarding reference becomes of type which appropriate, depending on what is T.
(and implicit conversion from lvalue to rvalue reference is forbidden by standard).
A cast is required literally because of that. Following code is incorrect, because you can't call foo(v), as v is a named object and it can be an lvalue:
void foo(int && a) { a = 5; }
int main()
{
int v;
foo(v);
std::cout << a << std::endl;
}
But if foo() is a template, it may become a function with int&, const int& and int&& arguments.
template<class T>
void foo(T && a) { a = 5; }
You would be able to call foo(v+5), where argument is a temporary, which can be bound to rvalue reference. foo will change the temporary object which stops to exist after function call. That's the exact action which move constructors usually have to do - to modify temporary object before its destructor is called.
NB: An rvalue argument would cease to exist earlier , either after its use or at end of function call.
Forwarding references are a special kind of reference syntax designed to preserve the value category of a function argument. I.e. non-template function
Object&& move(Object&& arg)
is not equal to std::move for Object, which declared something like (c++11):
template<class T>
std::remove_reference<T>::type&& move( T&& t );
In non-template function arg is an lvalue, in template it have same value category as expression used to initialize it. In template std::remove_reference<T>::type refers to T, so std::remove_reference<T>::type&& is a true rvalue reference to T - a way around T&& alternative meaning.
By analogy to function call above, if implicit conversion was possible, then it would be possible to call move constructor where copy constructor is appropriate but missing, i.e. by mistake. return static_cast<Object&&>(arg); results in initialization involving call to Object::Object(Object&&) by definition of return, return arg would call Object::Object(const Object&).
Template std::move is type-correct "wrapper" around the static_cast to facilitate "implicit" cast, to simplify code by removing repeated static_cast with explicit type from code.
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.
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.
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.
Can someone explain the output of the following code?
#include <iostream>
template <class T>
void assign(T& t1, T& t2){
std::cout << "First method"<< std::endl;
t1 = t2;
}
template <class T>
void assign(T& t1, const T& t2) {
std::cout << "Second method"<< std::endl;
t1 = t2;
}
class A
{
public:
A(int a) : _a(a) {};
private:
friend A operator+(const A& l, const A& r);
int _a;
};
A operator+(const A& l, const A& r)
{
return A(l._a + r._a);
}
int main ()
{
A a = 1;
const A b = 2;
assign(a, a);
assign(a, b);
assign(a, a + b);
}
The output is
First method
Second method
Second method
I don't see why. Shouldn't the last call to assign activate the first version, since (a+b) doesn't return a const A object?
An expression doesn't only have a value and a type, but it also has a value category. This category can be
An lvalue: These expressions generally refer to declared objects, references, functions or dereference results of pointers.
An xvalue: These are the result of generating an unnamed rvalue reference. Rvalue references are created by T&& instead of T&. They are a C++11 concept, and you can ignore them here. Mentioned only for sake of completeness.
An prvalue: These are the results of casts to non-reference types (like A(10)) or computing/specifying a value, like 42 or 2 + 3.
An lvalue reference requires an lvalue expression for initialization. That is, the following is invalid:
A &x = A(10);
The reason behind this is that only lvalue expressions refer to things that are suitable and intended for staying alive a longer time than only for the duration of the initialization. Like, a declared object is alive until exiting its block (if it was a local non-static variable) or until the end of the program (if it was declared outside functions and classes). The rvalue expression A(10) refers to an object that dies already when the initialization is finished. And if you said the following, it would not make any sense of all, because pure values like 10 don't have an address at all, but references require some sort of identity to which they bind, which in practice is implemented by taking the address of their target internally in compilers
int &x = 10; // makes totally no sense
But for const references, C++ has a backdoor. When initialized with a prvalue, a const lvalue reference will automatically lengthen the lifetime of the object, if the expression refers to an object. If the expression has a non-object value, C++ creates a temporary object with a value of that expression, and lengthens the lifetime of that temporary, binding the reference to that temporary:
// lifetime of the temporary object is lengthened
A const& x = A(10);
// lifetime of the automatically created temporary object is lengthened
int const& x = 10;
What happens in your case?
Now the compiler in your case, because you supply a temporary object, will choose the version that has a A const& parameter type rather than a A& parameter type.
(a + b) returns a temporary object, though, and can therefore only be bound to a constant reference.
a+b returns a temporary, if you were allowed to catch a non-const reference to it you would be able to change it and then what? The temporary goes out of scope, and the changes done to it can never be captured by the application. In C++03 temporaries will be bound to const references types.
By the way, this has nothing to do with templates. Rewrite your example to use straight 'A's and you will observe the same behavior.