Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ function returns a rvalue, but that can be assigned a new value?

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.

like image 209
haipeng31 Avatar asked Apr 05 '13 02:04

haipeng31


2 Answers

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

  • either from another lvalue reference
  • or something that can be converted to an lvalue reference of an intermediary type
  • or from an rvalue, but only if the lvalue reference we initialize is a const-reference

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.)

like image 94
jogojapan Avatar answered Oct 27 '22 01:10

jogojapan


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.

like image 39
dspeyer Avatar answered Oct 26 '22 23:10

dspeyer