45

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?

Joey.Z
  • 3,450
  • 4
  • 30
  • 59

5 Answers5

51

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.

Community
  • 1
  • 1
TemplateRex
  • 65,583
  • 16
  • 147
  • 283
  • The question is whether it is necessary to `const`-qualify the return value of `operator*` to prevent `(a * b) = c`. The question is not whether it is a good or bad idea to `const`-qualify the return value of `operator*`. – Oswald May 30 '13 at 11:37
  • But wait, why don't define const move ctor like `T(const T&&)`? – Artem Sobolev May 30 '13 at 11:42
  • 4
    @Barmaley.exe: because move semantics require a non-const object (how can you *move* -- ie. modify -- this object otherwise?). – syam May 30 '13 at 11:43
  • @Barmaley.exe I don't know the ins and outs of moving, but to me `const` and moving seems like a contradiction. – Oswald May 30 '13 at 11:43
  • @syam `mutable` would help. – Artem Sobolev May 30 '13 at 11:44
  • 2
    @Barmaley.exe: of course `mutable` would work but this defeats const-correctness: you'd have to make `mutable` pretty much every member of a movable class and then constness would have no meaning at all any more. – syam May 30 '13 at 11:46
  • `mutable` would help, but is very likely inapropriate. `mutable` is intended for member variables that do not belong to the state of the object that is visible to the outside. – Oswald May 30 '13 at 11:47
  • There is another question on this subject: http://stackoverflow.com/a/14101647/1190430. @syam you needn't mark every member of class as mutable — it's sufficient to create boolean flag `isMoved` in order to not delete moved resources. – Artem Sobolev May 30 '13 at 11:49
  • So the operator= of a user-defined class is not a typical assignment operator as for those built-in types. They behave a little different. Is this correct? – Joey.Z May 30 '13 at 11:57
  • 1
    @zoujyjs how user-defined assignment operators behave is up to their implementation. The point is that in general they should behave as similar as possible as they do for builtins. But because for class types they can also be called on rvalues, it used to be recommended to make rvalues const to inhibit such assignments. – TemplateRex May 30 '13 at 11:59
  • 3
    @Barmaley.exe: Well for once I don't agree at all with Andy (and his answer you linked to). You can bend the language into doing all sort of things, but bending the semantics is plain evil. Moving means modifying the visible state of both objects (moved-from/to), `const` means you can't modify the visible state, that's simply incompatible semantics-wise. Just because you can shoot yourself in the foot doesn't mean you should. – syam May 30 '13 at 12:11
  • 17
    The proper way to forbid that assignment is `Rational &operator=(const Rational& other) &;` – Johannes Schaub - litb May 30 '13 at 12:47
  • @JohannesSchaub-litb, Would you say that should be the default way to declare it now unless you want the different semantics (assuming a compiler that implements it)? – chris Jun 03 '13 at 22:59
7

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
Community
  • 1
  • 1
Andrew Durward
  • 3,471
  • 1
  • 16
  • 30
5

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.)

Bathsheba
  • 220,365
  • 33
  • 331
  • 451
  • A reference return type for `operator*` would be somewhat odd... – Oliver Charlesworth May 30 '13 at 11:34
  • Maybe you can explain why it's better for the return value to be non-const? Otherwise, this answer isn't worth much. How will this "annoy users of your class"? – interjay May 30 '13 at 11:34
  • You might want to write `a = (b * c).some_method_that_is_faster_on_non_const_self()`. But I admit I've never come across that situation. – Fred Foo May 30 '13 at 11:34
  • Oli: I've edited my answer now and made it clear that I don't mean having a return reference! – Bathsheba May 30 '13 at 11:35
  • The question is about having a const value return type, not a const reference return type. So this answer doesn't seem relevant. – interjay May 30 '13 at 11:36
  • What's a waffle?? And the question is about the return type, as evidenced by the last paragraph. In any case, your "answer" is just a personal opinion without any justification for it, just some vague mention of "annoying users" without explaining why. – interjay May 30 '13 at 11:42
3

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
Artem Sobolev
  • 5,196
  • 1
  • 19
  • 37
1

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;
}
 
Community
  • 1
  • 1
Alexander Oh
  • 20,413
  • 12
  • 65
  • 70