Question

The "Effective C++" Item 3 says "Use const whenever possible", and it gives an example like:

const Rational operator*(const Rational& lhs, 
                            const Rational& rhs);

to prevent clients from being able to commit atrocities like this:

Rational a, b, c;
...
(a * b) = c;   // invoke operator= on the result of a*b!

But isn't the non-reference return value of functions allready a rvalue? So why bother doing this?

Was it helpful?

Solution

The point is that for class types (but not for builtin 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 such (a * b) created by Rational::operator*. To enforce similar semantics as for builtin rvalues ("do as the ints do") some authors (Meyers including) recommended in C++98 to return by const-rvalue for classes with such operators.

However, in C++11 returning by const-rvalue is a bad idea as it will inhibit move semantics because const rvalues cannot bind to T&&.

In his notes An overview of the new C++ (C++11), Scott Meyers gives precisely the same example from his old book, and concludes that it is now considered poor design to add the const return value. The recommended signature is now

Rational operator*(const Rational& lhs, const Rational& rhs);

UPDATE: As implied by @JohannesSchaub-litb in the comments, in C++11 you can also use a reference qualifier on the assignment operator so that it will only accept lvalues as its left argument (i.e. the *this pointer, which is why this feature is also known as "rvalue references for *this"). You'll need g++ >= 4.8.1 (just released) or Clang >= 2.9 to make use of it.

OTHER TIPS

The const modifier on the return value is not necessary and can hinder move semantics. The preferred way of preventing assignment to rvalues in C++11 is to use "ref-qualifiers."

struct Rational
{
  Rational & operator=( Rational other ) &; // can only be called on lvalues
};

Rational operator*( Rational const & lhs, Rational const & rhs );

Rational a, b, c;

(a * b) = c; // error

Perhaps this is going to cost me rep points, but I disagree. Don't modify the expected return types of overloaded operators as it will annoy users of your class. i.e. use

Rational operator*(const Rational& lhs, const Rational& rhs);

(Of course, consting the parameters is good practice, and having constant reference parameters is even better as it means the compiler will not take deep copies. But don't have a constant reference return value in this case though as you'll get a dangling reference which is catastrophic. But do note that sometimes, taking a reference is slower than a pass by value. I think that doubles and ints come into that category on many platforms.)

Because you might be intended to write (a * b) == c instead i.e.

if ((a * b) = (c + d)) // executes if c+d is true

But you wanted to

if ((a * b) == (c + d)) // executes if they're equal

I guess what you would like to do according to your question is to declare the corresponding operator= private such that it is not accessible anymore.

hence you would like to overload the signature that matches (a*b) = c. I agree that the left part is an expression and therefore an rvalue would be a better match. however you are ignoring the fact that this is the return value of the function if you overload the function to return an rvalue it the compiler will complain about an invalid overloading as overloading rules don't consider return values.

As stated here the operator overload for assignment is always an inside class definition. if there would be a non-member signature like void operator=(foo assignee, const foo& assigner); the overload resolution could match the first part as an rvalue (then you could delete it or declare it private).

So you can choose from two worlds:

  • live with the fact that users can write stupid stuff like (a*b) = c which is not wrong but stores the value of c in an unaccessible temporary
  • use the signature const foo operator*(const foo& lhs, const foo& rhs) which dissallows the (a*b) = c and sacrifice move semantics

code

#include <utility>

class foo {
    public:
    foo() = default;
    foo(const foo& f) = default;
    foo operator*(const foo& rhs) const {
        foo tmp;
        return std::move(tmp);
    }
    foo operator=(const foo& op) const {
        return op;
    }
    private:
    // doesn't compile because overloading doesn't consider return values.
    // conflicts with foo operator=(const foo& op) const;
    foo && operator=(const foo& op) const; 
};


int main ( int argc, char **argv ) {
    foo t2,t1;
    foo t3 = t2*t1;
    foo t4;
    (t2 * t1) = t4;
    return 0;
}
 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top