2

My understanding is that, in the following function, the expression foo in the statement return foo; is an xvalue, because the object it denotes is expiring (even though foo is an lvalue in previous statements):

Foo bar()
{
    Foo foo;
    change(foo);
    return foo;
}

Such an expiring value is not covered by What expressions create xvalues?.

Does that change in the following case?

Foo bar(Foo&& foo)
{
    change(foo);
    return foo;
}

Is foo an xvalue in the return statement? And in particular, is it a candidate for move? And for RVO? Or should one use return std::move(foo)?

I do not know what the formal rule is for classifying the expression foo as an xvalue in the return statement of the first case, so I cannot test it in the second.

Community
  • 1
  • 1
ricab
  • 2,697
  • 4
  • 23
  • 28
  • 1
    possible duplicate of [What expressions create xvalues?](http://stackoverflow.com/questions/11581903/what-expressions-create-xvalues) – underscore_d Jul 20 '15 at 15:35
  • I don't think it is a duplicate, as I am asking about a very particular case – ricab Jul 20 '15 at 15:48
  • 1
    The chosen answer begins thusly: "An expression is an xvalue if it is: •the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type". Your function returns a value, not a reference or rvalue reference. Thus, your function does not return an xvalue, and your question is answered. Asking about a very particular case that is already covered in a comprehensive answer seems still to count as a duplicate to me. – underscore_d Jul 20 '15 at 15:52
  • 1
    I am not asking if the function returns an xvalue, I am asking if the expression foo is an xvalue in the statement return foo. – ricab Jul 20 '15 at 15:55
  • In your first example, depending on the type `Foo`, `return foo;` can result in a move construction. This happens *despite* the expression `foo` being an lvalue; there's a specific rule that says it can be *treated as an rvalue for overload resolution* in a return statement under certain circumstances (in C++14: the expression is the name of a local non-static variable or parameter). – dyp Jul 20 '15 at 16:22
  • *"I do not know what the formal rule is for classifying the expression foo as an xvalue in the return statement "* See, for example, http://en.cppreference.com/w/cpp/language/return – dyp Jul 20 '15 at 17:41
  • @dyp thanks for link, but I cannot find "xvalue" nor "expiring" in there – ricab Jul 20 '15 at 17:44
  • Because the name appearing in the return statement is treated as an rvalue, which is a super-category of xvalue. There is no difference for overload resolution between prvalue and xvalue AFAIK. – dyp Jul 20 '15 at 17:45
  • OK, but it does not offer a rule for xvalue covering this case, which I something I wanted to clarify. Also, according to that, I don't see why the second case is not eligible for moving (and it isn't, see http://melpon.org/wandbox/permlink/iwdxzADBoKcgzzhP) – ricab Jul 20 '15 at 17:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/83786/discussion-between-dyp-and-ricab). – dyp Jul 20 '15 at 18:01

1 Answers1

2

In that function, foo is an lvalue of type "rvalue reference to Foo". When you return it, since a copy has to be constructed (due to the type of the returned value), you are constructing a whole new value, which makes bar(...) an prvalue, as per §3.10.1.5:

A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [ Example: The result of calling a function whose return type is not a reference is a prvalue. The value of a literal such as 12, 7.3e5, or true is also a prvalue. — end example ]

Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction and the copy constructor is selected.

And yes, RVO applies here, assuming a move is not selected first. There's nothing special in that regard here. As per §12.8.31:

This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv- unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

[...]


To clarify, foo per se is an lvalue, but the statement:

return foo;

ultimately results (from the bar(...) expression) in a prvalue due to the fact that given that return type, the expression is equivalent to:

return Foo(foo);

which means that a temporary value, copied from foo is returned from the function bar.


Rereading the reply, it still does not make sense to me. You say Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction and the copy constructor is selected. Why is this true in one case and not the other?

When returning foo you have to create a new Foo value (because you are returning a copy) from the lvalue reference foo. This is done implicitly by the copy constructor. So return foo; is equivalent to return Foo(foo). Given that foo is an lvalue, the copy constructor is selected (and not the move constructor).

Now, when you have this new temporary value (constructed from foo), the value itself, which comes out of the expression bar(...), is a prvalue. So when you do:

auto res = bar(...);

You have to construct a Foo copy out of a prvalue. Since a prvalue is also an rvalue, the constructor with the rvalue reference parameter (move constructor) is selected.

Community
  • 1
  • 1
Shoe
  • 74,840
  • 36
  • 166
  • 272
  • Thanks for the answer Jefffrey, but the expression foo may be different things at different statements inside the function and I wanted to understand the return statement in particular. I edited the question to better reflect that. Perhaps my assumption that the return is an xvalue in the previous case is wrong? But that is motivated by the first bit in http://stackoverflow.com/a/18447976/563765 – ricab Jul 20 '15 at 16:02
  • *"And yes, RVO applies here."* Which copy/move is elided? – dyp Jul 20 '15 at 16:19
  • @dyp The one from `return foo;` to `auto res = bar(...);`. Assuming a move does not happen first. – Shoe Jul 20 '15 at 16:20
  • Thank you for the explanation. The only thing I don't get then is why the return value is electable for move construction in the first case but not in the second. See http://coliru.stacked-crooked.com/a/891d64fa536be62e, where I disabled RVO. – ricab Jul 20 '15 at 16:24
  • @Jefffrey Maybe I'm just confused about what's called *RVO* (as opposed to the more general *copy elision*). I thought RVO only applies to `return Foo();` where the temporary is copied into the return value. – dyp Jul 20 '15 at 16:25
  • @ricab Because the returned value is a *prvalue*, and not an *lvalue*, therefore the rvalue-reference constructor (move constructor) is selected. – Shoe Jul 20 '15 at 16:25
  • `return ..` is not an expression and therefore has no value category. I'm not entirely sure what you mean with that last section. – dyp Jul 20 '15 at 16:28
  • @dyp That's what can happen here. A temporary is constructed and the copy/move is elided. – Shoe Jul 20 '15 at 16:29
  • @dyp `return x` is a statement, but the `x` in there is an expression – ricab Jul 20 '15 at 16:30
  • How can foo be a prvalue if it is named? And what makes it different in second case? – ricab Jul 20 '15 at 16:30
  • @dyp Never said `return ..` has value category. I said that what *results* from that is an rvalue. Which is the rvalue that ultimately results in `bar(..)`. Also I'm pretty sure you knew already what I meant. – Shoe Jul 20 '15 at 16:31
  • Well you write "but the expression `return foo;` results in an rvalue". `foo` is never an rvalue, it can merely be treated as one, namely if [class.copy]p32 applies. – dyp Jul 20 '15 at 16:31
  • *"Also I'm pretty sure you knew already what I meant. "* No, I'm sorry. I really wasn't quite sure because of the confusing [class.copy]p32. – dyp Jul 20 '15 at 16:31
  • @ricab `foo` is not a prvalue, what gets returned from the function is an rvalue, and that's why the move happens when receiving the returned value from `bar(..)`. – Shoe Jul 20 '15 at 16:32
  • *"Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction"* This essentially is incomplete for the first example in the OP, where said exception of [class.copy]p32 applies, and foo is moved despite being an lvalue (if possible). I'm still not sure if you're simplifying or if there's a synchronization issue with the modified OP. – dyp Jul 20 '15 at 16:34
  • Ahh, so what determines eligibility for moving is not the category of the expression in the return statement, but the category of the expression consisting of the function call? – ricab Jul 20 '15 at 16:35
  • Rereading the reply, it still does not make sense to me. You say _Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction and the copy constructor is selected._ Why is this true in one case and not the other? – ricab Jul 20 '15 at 16:41
  • Nope, still confused: _When returning [...] Given that foo is an lvalue, the copy constructor is selected (and not the move constructor)._ - if it can be said of one case, it could be said of the other; _Now, when you have this new temporary value [...] (move constructor) is selected._ - why could this be said of one case and not the other. I suppose I should ask this as a new question, or is that shunned upon by SO practices? I could delete this one, or edit it again, but it would be a different question... – ricab Jul 20 '15 at 16:58
  • @ricab Where's the lvalue in the second case? – Shoe Jul 20 '15 at 17:01
  • In the first line of your reply: _`foo` is an lvalue of type "rvalue reference to `Foo`"_ – ricab Jul 20 '15 at 17:03
  • @ricab Yes, the object `foo` never escapes `bar` though. So in `auto res = bar(...);` where's the lvalue? You have `bar(...)` which results in a prvalue. So `auto res = ` selects the move constructor. – Shoe Jul 20 '15 at 17:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/83781/discussion-between-ricab-and-jefffrey). – ricab Jul 20 '15 at 17:05
  • To summarize, from my discussion with Jefffrey I understand that foo is an xvalue in the first case because the object it denotes is expiring, which is not the case in the second bar. – ricab Jul 20 '15 at 17:34