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.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 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. – skypjack May 05 '16 at 06:07
  • The inability to assign to rvalues of built-in type goes all the way back to C. – T.C. May 05 '16 at 06:23
  • @T.C. 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. – skypjack May 05 '16 at 06:26
  • @skypjack you asked for rationale. The standard doesn't contain rationale for the decisions. – xaxxon May 05 '16 at 06:29
  • voting to close as "primarily opinion based". There's no one here qualified to give you the answer you seem to want. – xaxxon May 05 '16 at 06:30
  • @xaxxon 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... – skypjack May 05 '16 at 06:32
  • 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. – xaxxon May 05 '16 at 06:34
  • @xaxxon 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. – skypjack May 05 '16 at 06:37
  • @skypjack are you asking how the rules of C++ lead to this difference, or are you asking what the rationale behind those rules is – M.M May 05 '16 at 07:53
  • @M.M I'd say the first one. The world _rationale_ has already been pointed out as _wrong_. – skypjack May 05 '16 at 08:53
  • @xaxxon 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. – skypjack May 05 '16 at 16:56

2 Answers2

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.

Harold
  • 91
  • 3
  • 1
    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 with – M.M May 05 '16 at 07:52
  • 1
    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. – Harold May 05 '16 at 07:58
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.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    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. – bogdan May 05 '16 at 17:34
  • 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. – skypjack May 05 '16 at 18:39
  • Why on earth would assignment do an lvalue-to-rvalue conversion on the *left* operand? – T.C. May 05 '16 at 19:15
  • [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. – bogdan May 05 '16 at 20:34
  • 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`. – bogdan May 05 '16 at 20:40
  • 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.) – bogdan May 05 '16 at 20:53
  • @bogdan Thank you for the clarification. Can I use parts of your comments to edit the answer? Obviously, I'll cite you for them!! :-) – skypjack May 05 '16 at 22:08