xvalues: differences between non class types and class types
Asked Answered
B

2

10

Consider the minimal example below:

#include<utility>

struct S { };

int main() {
    S s;
    std::move(s) = S{};
}

It compiles with no errors.
If I use non class types instead, I get an error.
As an example, the code below doesn't compile:

#include<utility>

int main() {
    int i;
    std::move(i) = 42;
}

The same happens with enums, scoped enums, and so on.
The error (from GCC) is:

using xvalue (rvalue reference) as lvalue

What's the rationale behind this?

I guess it's right, but I'd like to understand what's the reason for which I can do that with all the types but the non class ones.

Benedicto answered 5/5, 2016 at 5:55 Comment(11)
Are you saying that the standard doesn't define it for non class types, so as to bind to both lvalues and rvalues? Can you give more (all the) details by providing an answer? References to the standard itself would be appreciated.Benedicto
The inability to assign to rvalues of built-in type goes all the way back to C.Paryavi
@Paryavi Does the same apply more or less to all the other operators? As an example, if I define a operator++ for S, ++std::move(s) works, but ++std::move(i) does not.Benedicto
@Benedicto you asked for rationale. The standard doesn't contain rationale for the decisions.Lollipop
voting to close as "primarily opinion based". There's no one here qualified to give you the answer you seem to want.Lollipop
@Lollipop What? Have you read the comments above? Are you arguing about a wrong word used by a non native speaker? I can change rationale to whatever you want if it helps...Benedicto
What do you want? Do you want to know if the compiler is broken? What is missing from what dmitri and tc already told you? I just don't understand why you're confused about not being do 3=5 is wrong or needs significant rationalization.Lollipop
@Lollipop So, you are admitting that a response exists now. Why did you vote to close the question? Anyway, as you want, arguing with you about a vote to close the question is not what I want to do today morning. No problem at all, your opinion is welcome. Thank you.Benedicto
@Benedicto are you asking how the rules of C++ lead to this difference, or are you asking what the rationale behind those rules isEcumenicist
@Ecumenicist I'd say the first one. The world rationale has already been pointed out as wrong.Benedicto
@Lollipop I tried to answer to my own question with references to the standard, so as to show that it is not a primarily opinion-based question. I'm wrong for sure, but I deserve a review at least. :-) ... Would you like to help me correcting the answer? Thank you.Benedicto
B
1

I'm trying to reply to my own question with a bunch of links to the standard.
I'm quite sure that I'll write something that is terribly wrong and someone will come banding words with me.
Well, I did my best to explain how one can deduce from the standard what's described in the question.
Feel free to downvote if needed, but please let me know what's wrong so as to be able to fix the answer and understand the error.
Thank you.


3.9/8 (types):

An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not cv void.

5.2.2/10 (expressions, function call):

A function call is [...] an xvalue if the result type is an rvalue reference to object type

Thus std::move is an xvalue expression in either cases.

5.18/3 (assignment):

If the left operand is not of class type, the expression is implicitly converted [...] to the cv-unqualified type of the left operand.

This does not add useful information, but it's for the sake of completeness.

4.1/2 (lvalue-to-rvalue conversion):

Otherwise, if T has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.

Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

12.2 (temporary objects) does the rest.

So, as mentioned by @xaxxon in the comments, I was actually trying to do (let me write) 42 = 0; and it is not a valid expression in C++.

As correctly pointed out in the comments by @bogdan, the right part of the standard to which to refer in this case is 5.18/1 (Assignment):

All require a modifiable lvalue as their left operand [...]

While 5/2 and 5/3 clarify that the statement applies to built-in operators only.

Benedicto answered 5/5, 2016 at 16:47 Comment(7)
Actually, I don't think it's a good idea to think about it in terms of "I was trying to do 42 = 0;". 42 is a prvalue; the result of std::move(i) is an xvalue; they're both rvalues, but of essentially different kinds. I think the standard reference you're looking for is in [5.18p1]: [...] All require a modifiable lvalue as their left operand [...]. As clarified in [5p2] and [5p3], that statement applies to built-in operators only.Worse
Isn't 4.1/2 right? It says that the glvalue ends in a prvalue for it is not a class type, as mentioned. Anyway thank for the comment, I'll look a bit deeper in the section you pointed out.Benedicto
Why on earth would assignment do an lvalue-to-rvalue conversion on the left operand?Paryavi
[4.1p2] doesn't apply here. Conceptually, the glvalue-to-prvalue conversion happens when you have an expression that identifies the location of an entity (a glvalue) and you need a copy of the value that is stored at that location - that's the prvalue result of the conversion. When you're assigning to an object of a fundamental type, you're not interested in the value that's already there; you just need the location where the new value will be inserted, and a glvalue expression gives you exactly that, it doesn't need to be converted to a prvalue.Worse
Example: int i = 7; int j = 3;. Now, in the expression i = j, both i and j are glvalue (sub)expressions (lvalues, to be exact). We need a copy of the value stored at the location given by the evaluation of j in order to store it at the location identified by the evaluation of i. So, the lvalue-to-rvalue conversion is applied to j, but not to i.Worse
Clarification for the second sentence in my other comment: a glvalue is an expression that identifies the location of an entity. (I just realised that the way I wrote it was confusing; it could be read to mean that the entity is the glvalue, which is not correct - value categories are properties of expressions.)Worse
@Worse Thank you for the clarification. Can I use parts of your comments to edit the answer? Obviously, I'll cite you for them!! :-)Benedicto
T
5

C++ allows assignment to class object rvalues, but not primitive type rvalues;

An example,

string s1, s2;
s1 + s2 = "asdf"; // ok since rvalue s1 + s2 is an object

int i1, i2;
i1 + i2 = 10;  // error, since i1 + i2 is a primitive type

The same rule applies to your question. std::move(s) returns a rvalue of an object type, but std::move(i) returns a rvalue of a primitive type.

Trumpetweed answered 5/5, 2016 at 7:42 Comment(2)
Further info: it's been suggested to apply lvalue-ref qualifiers to standard containers' operator= by default, but it wasn't considered important enough to go ahead withEcumenicist
I noticed that. Such usage is not a valid use case, it would be good to have standard containers to be able to detect such usages itself.Trumpetweed
B
1

I'm trying to reply to my own question with a bunch of links to the standard.
I'm quite sure that I'll write something that is terribly wrong and someone will come banding words with me.
Well, I did my best to explain how one can deduce from the standard what's described in the question.
Feel free to downvote if needed, but please let me know what's wrong so as to be able to fix the answer and understand the error.
Thank you.


3.9/8 (types):

An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not cv void.

5.2.2/10 (expressions, function call):

A function call is [...] an xvalue if the result type is an rvalue reference to object type

Thus std::move is an xvalue expression in either cases.

5.18/3 (assignment):

If the left operand is not of class type, the expression is implicitly converted [...] to the cv-unqualified type of the left operand.

This does not add useful information, but it's for the sake of completeness.

4.1/2 (lvalue-to-rvalue conversion):

Otherwise, if T has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.

Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

12.2 (temporary objects) does the rest.

So, as mentioned by @xaxxon in the comments, I was actually trying to do (let me write) 42 = 0; and it is not a valid expression in C++.

As correctly pointed out in the comments by @bogdan, the right part of the standard to which to refer in this case is 5.18/1 (Assignment):

All require a modifiable lvalue as their left operand [...]

While 5/2 and 5/3 clarify that the statement applies to built-in operators only.

Benedicto answered 5/5, 2016 at 16:47 Comment(7)
Actually, I don't think it's a good idea to think about it in terms of "I was trying to do 42 = 0;". 42 is a prvalue; the result of std::move(i) is an xvalue; they're both rvalues, but of essentially different kinds. I think the standard reference you're looking for is in [5.18p1]: [...] All require a modifiable lvalue as their left operand [...]. As clarified in [5p2] and [5p3], that statement applies to built-in operators only.Worse
Isn't 4.1/2 right? It says that the glvalue ends in a prvalue for it is not a class type, as mentioned. Anyway thank for the comment, I'll look a bit deeper in the section you pointed out.Benedicto
Why on earth would assignment do an lvalue-to-rvalue conversion on the left operand?Paryavi
[4.1p2] doesn't apply here. Conceptually, the glvalue-to-prvalue conversion happens when you have an expression that identifies the location of an entity (a glvalue) and you need a copy of the value that is stored at that location - that's the prvalue result of the conversion. When you're assigning to an object of a fundamental type, you're not interested in the value that's already there; you just need the location where the new value will be inserted, and a glvalue expression gives you exactly that, it doesn't need to be converted to a prvalue.Worse
Example: int i = 7; int j = 3;. Now, in the expression i = j, both i and j are glvalue (sub)expressions (lvalues, to be exact). We need a copy of the value stored at the location given by the evaluation of j in order to store it at the location identified by the evaluation of i. So, the lvalue-to-rvalue conversion is applied to j, but not to i.Worse
Clarification for the second sentence in my other comment: a glvalue is an expression that identifies the location of an entity. (I just realised that the way I wrote it was confusing; it could be read to mean that the entity is the glvalue, which is not correct - value categories are properties of expressions.)Worse
@Worse Thank you for the clarification. Can I use parts of your comments to edit the answer? Obviously, I'll cite you for them!! :-)Benedicto

© 2022 - 2024 — McMap. All rights reserved.