Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning Rvalue returned from function to another Rvalue

class Test {

public:

    int n1;

};

Test func() {

    return Test();

}

int main() {

    func() = Test();

}

This doesn't make sense to me. How and why is this allowed? Is it undefined behavior? If a function returns an rvalue, then how is it possible to set an rvalue to another rvalue? If I tried this with any primitive types, it would give me an error like I expect.

I know that lvalues are a place in memory, so is the function creating a temporary lvalue (rvalue?) and assigning it to another lvalue? Can someone explain how this syntax works?

like image 398
Greg M Avatar asked Feb 13 '16 09:02

Greg M


People also ask

Can a function return an rvalue?

Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue. Once you call return on an object, the name of the object does not exist anymore (it goes out of scope), so it becomes an rvalue.

Can you modify an rvalue?

rvalue of User Defined Data type can be modified. But it can be modified in same expression using its own member functions only.

Is an rvalue reference an rvalue?

An rvalue reference is formed by placing an && after some type. 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.

Can you pass an lvalue to an rvalue reference?

In the example, the main function passes an rvalue to f . The body of f treats its named parameter as an lvalue. The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g ). You can cast an lvalue to an rvalue reference.


2 Answers

The value category of the function call expression is in fact an rvalue.

Indeed, you may not call the copy assignment operator on rvalue primitives. The historical definition of rvalues in C was in fact the distinction that they may not be on the left side of an assigment.

The assignment operators of classes are a bit different, though. They're regular member functions. And no rule prevents calling member functions of rvalues. Indeed, it's often quite useful when the functions have side-effects.

How it works, well, the copy assignment operator is called on the temporary, the operator copies the right hand argument, changing the state of the temporary. After the statement, the temporary object is discarded. There is no UB, just pointless copying.

You can prevent calling copy assignment on an rvalue, by declaring the operator with a reference qualifier like this: Test& operator=(Test) & = default;. Ref-qualifiers were added only later in c++11, so (implicit) copy assignment couldn't have been specified to be ref-qualified earlier. Presumably, C++11 didn't change the qualifier of implicit copy constructor to prevent breaking old code that does assign to an rvalue, even though such assignment does seem quite pointless. The High Integrity C++ Coding Standard recommends that you do use ref qualifier with user defined copy assignment operators.

like image 121
eerorika Avatar answered Sep 28 '22 06:09

eerorika


There's a pretty steep learning curve as far as value categories are concerned (at least to me), but I believe you have got the terminology right on your example. So

func()

indeed returns a prvalue and from the C++ Standard par. 3.10.5 (I only have the current draft, your paragraph number may vary) we read :

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 the assignment operator, which is a member function, as in the example mentioned in the standard is an exception to the rule, allowing rvalues to be modified.

This has spawned much criticism in the programming world, with its most extreme example this C++ FQA excerpt :

(Yes, sugar-coated death traps, you clueless cheerleaders. X& obj=a.b().c() – oops, b() is a temporary object and c() returns a reference into it! Shouldn't have assigned that to a reference. Not many chances for a compiler warning, either.)

But in real C++ programming it has industrial applications like the named parameter idiom :

std::cout << X::create().setA(10).setB('Z') << std::endl;
like image 37
Lorah Attkins Avatar answered Sep 28 '22 07:09

Lorah Attkins