tags:

views:

297

answers:

6

The original question is related to overloading operator= and I like to share my findings as it was nontrivial for me to find them. I cannot imagine reasonable example to use (a=b) as lvalue. With the help of IRC and google I've found the next article: http://msdn.microsoft.com/en-us/magazine/cc301415.aspx

it provides two examples.

  (a=b)=c

  f(T& );
  f(a=b)

but both a bit not good, and I believe that it is bad practice. The second one give me the same feeling. Could you provide more good examples why it should be non constant?

+2  A: 

I've spent some time and here is my example:

class A
{
public:
    const A& operator= (const A& a) {return *this;}
};

int main(int argc, char* argv[])
{
    A a1;
    A& a2 = a1;
    A& a3 = (a2 = a1);
}

and the compiler output: : error C2440: 'initializing' : cannot convert from 'const A' to 'A &'

I've checked it on MS VS 2010, but is it true on other platforms? And if this example is sufficient condition for = to be non const?

outmind
Also compile error on g++.
KennyTM
Thanks you! What is your ideas about sufficiency?
outmind
+1  A: 

Why should it be const? If you're assigning to it, obviously it's modifiable. That would be artifically limiting.

As for use cases, why not:

T &local = t1 = t2 = t3;

In this example, local isn't const.

zildjohn01
outmind
Why can you assign to a pointer?
KennyTM
@outmind: In this case, t3 is a type that is convertible and/or assignable to t2, t2 is a type that is convertible and/or assignable to t1, and t1 is a type that is convertible and/or assignable to T*.
Bill
outmind
zildjohn01
Agreed. And it's the same as my example.
outmind
+8  A: 

One good reason is that one of the requirements in the standard for a class X to be useable in the standard containers is that the expression a = b must have type X& (where a is an lvalue of type X and b is an rvalue of type X).

Charles Bailey
Good catch, mate :)
Johannes Schaub - litb
Thanks! Nice shot.I'll vote this up.(But probably this is related to bad practice in STL. Can you give a pure C++ example?)
outmind
KennyTM
Charles Bailey
I'd be interested in any decent (or even half-decent) answers to KennyTM's question. About the best rationale I could find on the web was here: http://www.keyongtech.com/4706456-why-the-copy-assignment-operator/2#18, but even that is pretty weak (the code that requires Assignable to return a non-const ref could be trivially changed to "`myValue = newValue; return myValue;`" if Assignable were instead defined to return a const ref).
Michael Burr
Dennis Zickefoose
Michael Burr
Dennis Zickefoose
@Dennis: I guess you're right, and obviously things aren't falling over left and right due to problems - however, something about it just doesn't feel right to me. But I should probably just move along...
Michael Burr
@Michael> it just doesn't feel right to meme too... but seems it is due to consistency with C, habits of old programmers so on@Dennis: (so *this must be non-const - wrong) It is not about const functions, it is about const of a result.
outmind
@outmind: The point is you [almost always] return `*this` from `operator=`. `*this` is non-const, both in this scope and the calling scope, so what benefit can be gained by casting it to const before returning?
Dennis Zickefoose
@Dennis: I was only against the word "must", as it doesn't. But your point is good - as what I'm trying to find is a good example or counterexample for usage of nonconst reference as operator= result.
outmind
Now it more looks like the result of C++ evolution process not the well-founded obvious claim.
outmind
A: 

If we consider three auto_ptr a, b and c, the operator = has to return a non-const reference so that you can do multiple assigns since assigning the pointer to another modifies the first.

so if we have a = b = c, the following happens: c is assigned to b (c is modified to point to null), the operator returns a reference to b the reference returned by (b = c) is assigned to a, it is thus modified to point to null, which is only possible if the reference is non-const.

Razispio
+6  A: 

Most probably because this is how the native types of the language work. e.g.:

int x = 0, y = 1, z = 2;
(x = y) = z;

AFAIK, Dr. Stroustrup said that it is a good thing to have consistency in the language. i.e. user-defined types should behave just like native types.

AraK
Thanks! Good example.C backward compatibility and consistence.
outmind
Except that the C++ behavior is in fact different than C. From the C99 standard (6.5.16/3 "Assignment operators"): "An assignment expression has the value of the left operand after the assignment, but is not an lvalue." It would be interesting to know what Stroustrup's thinking was on why this change was necessary. I can't readily think of a good, desirable use-case (but that just means someone needs to tell me or remind me what it is).
Michael Burr
Consistency with what? In C language the result of assignment is an rvalue. C++ is *not* consistent with C in that regard.
AndreyT
@AndreyT: outmind mentioned C compatibility the preceding comment, but even if you consider being consistent with C++ itself: technically doesn't `(x = y) = z;` result in undefined behavior for built-in types since there is no sequence point between the 2 stores to `x`? Is it well defined whether x ends up with 1 or 2 (or anything else)?
Michael Burr
Michael: surely the parentheses introduce a sequence point to ensure that x=y happens before =z? In the same way that the result of (x+y)*z adds x and y before the multiply?
Peter
@Peter: No on both accounts. Parentheses do not introduce a sequence point and the behavior of `(x = y) = z` is indeed undefined. Moreover, in `(x+y)*z` there's no guarantees of what happens first and what happens next. Parentheses only alter the mathematical meaning of the expression, but introduce no temporal restrictions, no sequence points. The compiler is free to evaluate it in any order it pleases, as long as the result is correct. It can do it as `x*z + y*z`. Or as `x + (y*z) + x*(z-1)`.
AndreyT
I have the next phrase in my "The C programming language" 2nd:"A compiler's license to treat mathematically associative operators as computationally associative is revoked."If I correctly understand it your "mathematical" transformation are not permitted.(x+y)*z equal expr0 = x+y; expr = expr0 * z;Of course all safe optimizations are allowed.
outmind
@outmind: No, that's of course is not true. I don't know how you managed to derive that conclusion from that quote. The compiler is allowed to to absolutely all and any transformations as long as the observable behavior of the program is not changed. As for the situations with no sequence points, all bets are off: the language says absolutely nothing about any temporal orderings.
AndreyT
@outmind: Your example with `expr0 = x+y; expr = expr0 * z;` would only apply for user-defined types. For built-in types it is absolutely incorrect. You incorrectly assume the existence of a sequence point where there's none in reality.
AndreyT
@AndreyT, notice that in C++0x, `(x = y) = z;` is well-defined. value computation of the left operand of an assignment expression happens before the assignment, and the assignment happens before value computation of the result of an assignment. Thus assignment of `x = y` is done, then it's lvalue result is computed, and then `result = z` is done. These are well ordered in C++0x.
Johannes Schaub - litb
@Michael Burr ^^
Johannes Schaub - litb
@Johannes Schaub: Well, yes the problem with the built-in assignment specification in C++ has been a known issue for quite a while. How did they solve it in C++0x? An official sequence point in the assignment?
AndreyT
@AndreyT they solved it like i described above. Sequenced-before is a relation on two evaluations of expressions, and an evaluation can contain value computation (lvalue/rvalue computation) and initiation of side effects. And value computation of both operands of "op=" is sequenced-before the actual assignment, which in turn is sequenced before the value computation of its result. Thus, in `(x = y) = z` both assignments are sequenced transitively and thus do not cause undefined behavior. Notice that C++0x has no sequence points anymore, but instead sequenced-before relations.
Johannes Schaub - litb
@Johannes Schaub: Ah, OK. No sequence points in C++0x.
AndreyT
+2  A: 

Andrew Koenig wrote a post about this a long time ago. A lot of it comes down to doing what people expect under slight unusual circumstances. The example he gives is that in C, return x=y; always means the same thing as x=y; return x;. In C++, if you return essentially anything other than a reference (including a const reference), the two can mean different things.

Edit: Sorry, I linked to the wrong post. Try this one. The problem arises from the fact that a T const & can bind to a temporary instead of the "real" object, so what happened with the code above was that it created a temporary, copied the object into it, bound the reference to it, destroyed the temporary, then returned the (now dangling) reference.

Jerry Coffin
How? How can they mean different things if you return something other than reference?
AndreyT