The code is as follows:
#include <iostream>
using namespace std;
class A {
};
A rtByValue() {
return A();
}
void passByRef(A &aRef) {
// do nothing
}
int main() {
A aa;
rtByValue() = aa; // compile without errors
passByRef(rtByValue()); // compile with error
return 0;
}
The g++ compiler gives the following error:
d.cpp: In function ‘int main()’:
d.cpp:19:23: error: invalid initialization of non-const reference of type ‘A&’ from an rvalue of type ‘A’
d.cpp:12:6: error: in passing argument 1 of ‘void passByRef(A&)’
It says that I can't pass an rvalue as an argument of a non-const reference, but what I'm confused about is why I can assign to this rvalue, just as the code shows.
Passing the rvalue rtByValue()
to a function that expects an lvalue reference doesn't work because this would require the lvalue reference argument to be initialized from an rvalue. §8.5.3/5 describes how lvalue references can be initialized – I won't quote it in full, but it basically says that an lvalue reference can be initialized
Since the argument we need to initialize is not a const-reference, none of this applies.
On the other hand,
rtByValue() = aa;
i.e., assigning to a temporary object, is possible because of:
(§3.10/5) An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [ Example: a member function called for an object (9.3) can modify the object. — end example ]
So this works only because A
is of class-type, and the (implicitly defined) assignment operator is a member function. (See this related question for further details.)
(So, if rtByValue()
were to return, for example, an int
, then the assignment wouldn't work.)
Because you can (but shouldn't!) override operator= such that calling it on an rvalue makes sense. Consider the following code:
#include<iostream>
using namespace std;
class foo;
foo* gotAssigned = NULL;
int assignedto = -1;
class foo {
public:
foo(int v) : val(v) {}
foo& operator=(int v) {
assignedto=v;
gotAssigned = this;
val = v;
return *this;
}
int val;
};
foo theFoo(2);
foo returnTheFooByValue() {
return theFoo;
}
main() {
returnTheFooByValue()=5;
cout << "[" << assignedto << "] " << theFoo.val << " versus " << gotAssigned->val << endl;
}
Now let's compile it a few ways:
$ g++ -O0 -o rveq rveq.cc && ./rveq
[5] 2 versus 5
$ g++ -O1 -o rveq rveq.cc && ./rveq
[5] 2 versus 2
$ g++ -O4 -o rveq rveq.cc && ./rveq
[5] 2 versus -1218482176
I can't promise you'll see the same results.
As you can see, the assignment happens, but any attempt to use the object that got assigned results in implementation-specific behaviour.
Incidentaly, this only applies to user-defined types. This code:
int v(){
return 2;
}
main(){
v()=4;
}
doesn't compile.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With