18

In Effective C++, Item 3, Scott Meyers suggests overloading operator* for a class named Rational:

    class Rational { ... };
    const Rational operator*(const Rational& lhs, const Rational& rhs);

The reason for the return value being const-qualified is explained in the following line: if it were not const, programmers could write code such as:

    (a * b) = c;

or, more probably:

     if (a*b = c)

Fair enough. Now I’m confused as I thought that the return value of a function, here operator*, was a rvalue, therefore not assignable. I take it not being assignable because if I had:

    int foo();
    foo() += 3;

that would fail to compile with invalid lvalue in assignment. Why doesn’t that happen here? Can someone shed some light on this?

EDIT: I have seen many other threads on that very Item of Scott Meyers, but none tackled the rvalue problem I exposed here.

qdii
  • 12,505
  • 10
  • 59
  • 116
  • 2
    For class types, lvalue/rvalueness and assignability are orthogonal. There are lvalues that you cannot assign to, and there are rvalues that you can assign to. If you think of lvalue as "left side" and rvalue as "right side", forget that. C++ uses the terms lvalue and rvalue in a different way. – fredoverflow Jan 12 '12 at 10:53

1 Answers1

30

The point is that for class types, a = b is just a shorthand to a.operator=(b), where operator= is a member function. And member functions can be called on rvalues.

Note that in C++11 you can inhibit that by making operator= lvalue-only:

class Rational
{
public:
  Rational& operator=(Rational const& other) &;
  // ...
};

The & tells the compiler that this function may not be called on rvalues.

celtschk
  • 19,311
  • 3
  • 39
  • 64
  • Or in other words, the * operator returns a temporary object. Just like, say, a standalone `Rational()` which you can use as an lvalue. – Mr Lister Jan 12 '12 at 09:08
  • Also, there is at least one legitimate use-case for assignable return values: operator[]. It would be weird if you couldn't provide an overload that does not act like an array indexer. – Tamás Szelei Jan 12 '12 at 09:09
  • @TamásSzelei: operator[] typically returns a reference to an object, which is an lvalue, so this doesn't apply. However, sometimes a proxy object is returned (e.g. for allowing multiple indices), and in that case indeed assignment on rvalue is needed. – celtschk Jan 12 '12 at 09:11
  • 1
    @MrLister: No, `Rational()` is *not* an lvalue. For example, you cannot take its address (well, unless you *explicitly* overloaded the address-of operator, in which case it again is just a member function call). – celtschk Jan 12 '12 at 09:37
  • Oh... hm, I can write `Rational rat; Rational() = rat;` and `void* p = &Rational();` which makes it sound like an lvalue. Sorry. – Mr Lister Jan 12 '12 at 10:11
  • 1
    @MrLister: No, you *can't* write `void* p = &Rational();` without writing your own `operator&`. Just ask your compiler. More importantly, you cannot pass it to a function taking a non-const lvalue reference. – celtschk Jan 12 '12 at 10:15
  • This is getting too off-topic, but I have to know what declaration of Rational you used. And which compiler. – Mr Lister Jan 12 '12 at 10:19
  • @MrLister: celtschk is right: [you can't take the address of a temporary object](http://stackoverflow.com/q/2280688/20984) (unless you bind it to a const reference, which is an lvalue). – Luc Touraille Jan 12 '12 at 11:00
  • `class C{public:int m; C():m(0){}}; int main(){void*p=&C(); return 0;}` compiles for me, but I'm going to test with a more modern compiler later. (Currently on an older, company-mandated version of MSVC.) – Mr Lister Jan 12 '12 at 11:21
  • @MrLister: That's just one of MSVC's many "extensions" to the language; you can't use that compiler to check whether code is conformant. Other compilers will at least issue a warning for your code, and Clang will reject it. – Mike Seymour Jan 12 '12 at 11:51