Why is it possible to assign to an rvalue of class type?
Asked Answered
M

2

14

Why is this code compiling? I thought that rvalues returned by the constructor are not located in memory and therefore can't be used as lvalues.

class Y {
public :
    explicit Y(size_t num = 0) {}
};

int main() {
    Y(1) = Y(0); // WHAT?!?
    return 0;
}
Matthewmatthews answered 17/1, 2016 at 6:13 Comment(1)
The member function operator= may be called on any object. Your code is the same as Y(1).operator=(Y(0));Bladderwort
J
15

The synthesized assignment operator is declared as one of these (if it can be synthesized and isn't declared as deleted) according to see 12.8 [class.copy] paragraph 18:

  • Y& Y::operator=(Y const&)
  • Y& Y::operator=(Y&) ()

That is, like for any other member function which isn't specifically declared with ref-qualifiers it is applicable to rvalues.

If you want to prevent a temporary object on the left hand side of the assignment you'd need to declare it correspondingly:

class Y {
public :
    explicit Y(std::size_t num = 0);
    Y& operator= (Y const&) & = default;
};

The standard uses the name ref-qualifier for the & before the = default. The relevant proposal is N2439. I don't know where there is a good description of ref-qualifiers. There is some information at this question.

Jespersen answered 17/1, 2016 at 6:29 Comment(6)
I tried this and it works. What is the '&' just before '= default' called and where can I read more about this unfamiliar syntax?Matthewmatthews
@JohnDifool here is another thread on the topic. If you search stackoverflow for "ref qualifier" you should find a few moreBladderwort
@JohnDifool: my answer mentioned "reference qualifiers" but I agree that this informal name isn't really that helpful and I have changed the answer accordingly.Rove
I found this in C++ Primer book: "Reference Qualifier: Symbol used to indicate that a nonstatic member function can be called on an lvalue or an rvalue. The qualifier, & or &&, follows the parameter list or the const qualifier if there is one. A function qualified by & may be called only on lvalues; a function qualified by && may be called only on rvalues." I may need to do some digging to understand what the last part means.Matthewmatthews
After doing more reading and more thinking, I still have one question if I may: You suggestion for adding "Y& operator= (Y const&) & = default;" works but isn't it equivalent as saying that you need to default 'synthetized' behaviour? In other word, why does it work with 'default' while I believe it should have been 'delete'?Matthewmatthews
@JohnDifool: there is a difference between the declaration of the operator and its behavior. Its synthesized declaration tries to be as general as possible and, thus, allows assignment to rvalues. The synthesized definition asked for using = default just realizes an implementation which effectively does an assignment for each subobject. If you = deleted the operation all assignments would be prohibited as the declaration of any assignment operator inhibits of any other assignment. To prohibit assignments to rvalues you'd also = delete the && version not the & version.Rove
P
2

Not sure where you got that specific rule of thumb. If any, a rule of thumb would be (from Scott Meyers): if it has a name, it's an lvalue.

In this case, you're creating a temporary object and passing it to an assignment method/function. There is no problem with that. In fact, it might even make sense to do so, as in

// Applies foo to a copy, not the original.
(Y(1) = y).foo()

It is true that Y(*) don't have names here, though, and hence they are rvalues.

Primula answered 17/1, 2016 at 6:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.