C++ why the assignment operator should return a const ref in order to avoid (a=b)=c

14,173

Solution 1

(x=y) means x.operator=(y), which returns the object x. Therefore, (x=y)=z means (x.operator=(y)).operator=(z). The expression in parens sets x to y and returns x, and then the outer bit sets x to z. It does not set y to z as you might expect, and as the expression x = y = z does.

This behavior is counter-intuitive (they should all be equal after the assignment, right?); returning a const reference makes it impossible and avoids the problem.

Solution 2

There is no need to avoid this, unless the book is aimed at programmers that commonly write (x=y)=z when they mean x=y=z. In practice, nobody in their right mind writes that, so the precaution is entirely unnecessary. It also forbids some other terse constructs, such as (x=y).nonConstMember(), that hardly anyone writes but that might be useful in some contexts (although they shouldn't be over-used).

@ybungalobill is right, get a better book.

Solution 3

As far as I know, assignment operators do not return const references in idiomatic C++. The Standard types do not return const references either.

std::string a, b, c;
(a = b).clear(); // no objection from compiler

All of my custom assignment operators have returned a non-const reference.

When in doubt, check the Standard library. It's not flawless, but it definitely gets basic things like this correct.

Solution 4

I would look at the behavior of the built-in types.

When defining your own types it is preferable that the operators behave the same way as the built-in types. This allows easy adoption of your classes without having to dig into your code to see why they behave differently from expected.

So if we look at integers:

int main()
{
    int x = 5;
    int y = 6;
    int z = 7;

    (x = y) = z;
    std::cout << x << " " << y << " " << z << "\n";
}

This works with y unchanged and x being assigned 7. In your code i would expect your assignment operator to work the same way. The standard assignment operator definition:

Array& Array::operator=(Array const& rhs)
{
    /* STUFF */
    return *this;
}

Should do that just fine (assuming /* STUFF */ is correct).

Solution 5

The only reason I can see is that this book was written to explain C++ to C programmers (or by an author whose C understanding is better than C++ understanding). Because for a C programmer, the expression (x = y) = z is invalid for built-in types, and he probably will to try to get the same behavior with its user-defined types.

However, C and C++ are different languages, and in C++ the expression (x = y) = z is valid even for built-in types. So if you want to have the same behavior for your user-defined types, you should return a non-const reference in the operator =.

I would advise you to get a better book, one that does not make the confusion between C and C++. They are not the same langages, even if they derive from a common base.

Share:
14,173
alampada
Author by

alampada

Updated on June 15, 2022

Comments

  • alampada
    alampada almost 2 years

    I am reading a book about C++ and more precisely about the operator overloading.

    The example is the following:

    const Array &Array::operator=(const Array &right)
    {
    // check self-assignment
    // if not self- assignment do the copying
    return *this; //enables x=y=z
    }
    

    The explanation provided by the book about returning const ref instead of ref is to avoid assignments such as (x=y)=z. I don't understand why we should avoid this. I understand that x=y is evaluated first in this example and since it returns a const reference the =z part cannot be executed. But why?

  • James
    James over 13 years
    While the behavior sounds counter-intuitive, it isn't a "problem" because it is something the programmer would have intentionally done (Which is why they used brackets)
  • Fred Foo
    Fred Foo over 13 years
    Who would expect (x=y)=z to set y to z? This is very contrived "problem" and a C++ book shouldn't spend time explaining precautions against it. (x=5)=z doesn't set 5 to z.
  • Asher Dunn
    Asher Dunn over 13 years
    If you expect assignment to be associative (like comparison is), you would expect all three to be equal at the end. Naive, but a reasonable first-glance interpretation. I agree that this form shouldn't generally be used and shouldn't be emphasized in the book, but it's good to help the compiler help you not shoot yourself in the foot whenever possible =)
  • Fred Foo
    Fred Foo over 13 years
    The Boost folk commonly return mutable refs, too.
  • Fred Foo
    Fred Foo over 13 years
    The first thing to be understood about assignment is that it is not associative. Every imperative programming textbook that I've seen explains that in chapter 1 or 2. Your comment about mistyping (x=y) == z makes more sense.
  • Martin York
    Martin York over 13 years
    It may not be intuitive. BUT it is exactly the way it works with integers. I think mimicking the behavior of the standard types is more important than trying to protect people from obscure situations.
  • Ankit Roy
    Ankit Roy over 13 years
    +1. A programmer "accidentally" writing (x=y)=z seems about as likely (and as necessary to guard against) as a programmer accidentally writing x=y+system("rm -rf /") when they meant x=y.
  • Pablo Ariel
    Pablo Ariel over 8 years
    I want to be able to do: (SelectedMesh = Mesh1).SetTexture(tex); or (Normal = l1.Cross(l2)).Normalize(); . What benefits gives me to return a const in such assignments?
  • Adrian McCarthy
    Adrian McCarthy over 7 years
    But I have seen if ((x = y) = z) ... when the author meant if ((x = y) == z) ....
  • Daniel Ford
    Daniel Ford over 2 years
    In support of @MartinYork is Donnie Pinkston at CalTech who says in his C++ Operator Overloading Guidelines, courses.cms.caltech.edu/cs11/material/cpp/donnie/cpp-ops.htm‌​l, '''The rule of thumb is, "If it's good enough for ints, it's good enough for user-defined data-types."