Why the copy assignment operator is written to return non-constreference to this?

S

Singulus

Introduction:

It is good advice to write the return type of a function that returns
by value as a const:

SomeType GetSomeType(...); // BAD
const SomeType GetSomeType(...); // GOOD

The above is good because it prevents the function return value to be
used as a temporary:

GetSomeType().DoStuff();

This is especially useful in the case of operator functions that
creates objects, for example:

Vector3 operator+( const Vector & lhs, const Vector & rhs ); // BAD
const Vector3 operator+( const Vector & lhs, const Vector & rhs ); //
GOOD
(vec1 + vec2).Normalize(); // BAD variant pass this non-nonse, GOOD
stops it at compile time


The Question:

Why the same principle is not used for the copy assignment of classes?

Currently, the canonical copy assignment operator function prototype
is written like this:

class SomeType
{
SomeType & operator=( const SomeType & lhs );
};

This will pass a non-sense code like the following. The non-sense is
not the same as with the temporary objects but still is not the best
that can be done to restrict the writing of non-sense which is quite
easily and often unforgiving with C++:

SomeType t, u;
....
(t = u).DoStuff();

If we write simply append a const, this will still preserve the
behaviour when we have a chain of assignments because the function
itself takes a const reference to an object of the same class, so it
matches with the return value:

class SomeType
{
const SomeType & operator=( const SomeType & lhs );
};

SomeType t, u, v;
....
t = u = v;
 
I

Ian Collins

Singulus said:
Introduction:

It is good advice to write the return type of a function that returns
by value as a const:

SomeType GetSomeType(...); // BAD
const SomeType GetSomeType(...); // GOOD

The above is good because it prevents the function return value to be
used as a temporary:

GetSomeType().DoStuff();
Why is this good?
This is especially useful in the case of operator functions that
creates objects, for example:

Vector3 operator+( const Vector & lhs, const Vector & rhs ); // BAD
const Vector3 operator+( const Vector & lhs, const Vector & rhs ); //
GOOD
(vec1 + vec2).Normalize(); // BAD variant pass this non-nonse, GOOD
stops it at compile time
Why is this good?

The Question:

Why the same principle is not used for the copy assignment of classes?
Because it isn't good?
 
J

joseph cook

Introduction:

It is good advice to write the return type of a function that returns
by value as a const:

SomeType GetSomeType(...); // BAD
const SomeType GetSomeType(...); // GOOD

The above is good because it prevents the function return value to be
used as a temporary:

GetSomeType().DoStuff();

This is especially useful in the case of operator functions that
creates objects, for example:

Vector3 operator+( const Vector & lhs, const Vector & rhs ); // BAD
const Vector3 operator+( const Vector & lhs, const Vector & rhs ); //
GOOD
(vec1 + vec2).Normalize(); // BAD variant pass this non-nonse, GOOD
stops it at compile time

The Question:

Why the same principle is not used for the copy assignment of classes?

Primarily because base types (int, etc) return references, and its
considered good practice for your new types to behave semantically
like the built-in types. Someone using your class might expect as
much.

Is this a hard and fast rule? No. I don't expect you'll get any
grief or find any dangerous dragons down the corridor for returning
const ref.

My advice is to do as you like in this instance.

Joe Cook
 
A

Andrey Tarasevich

Singulus said:
It is good advice to write the return type of a function that returns
by value as a const:

SomeType GetSomeType(...); // BAD
const SomeType GetSomeType(...); // GOOD

The above is good because it prevents the function return value to be
used as a temporary:

Huh? The return value of both of the above functions is a temporary. One
is qualified with 'const' the other is not, but both are temporaries. So
what does "prevents [...] to be used as a temporary" supposed to mean?
GetSomeType().DoStuff();

Firstly, this will work for const-methods regardless of whether you add
'const' to the return type or not. What you were trying to do,
apparently, is prohibit modifying operations on the temporary. This
might be a good idea in many cases. But, secondly, there are q few
pretty useful programming techniques out there which rely on this
temporary being modifiable.
This is especially useful in the case of operator functions that
creates objects, for example:

Vector3 operator+( const Vector & lhs, const Vector & rhs ); // BAD
const Vector3 operator+( const Vector & lhs, const Vector & rhs ); //
GOOD
(vec1 + vec2).Normalize(); // BAD variant pass this non-nonse, GOOD
stops it at compile time

So far you failed to explain why one is "GOOD' and other is "BAD".
The Question:

Why the same principle is not used for the copy assignment of classes?

Currently, the canonical copy assignment operator function prototype
is written like this:

class SomeType
{
SomeType & operator=( const SomeType & lhs );
};

This will pass a non-sense code like the following. The non-sense is
not the same as with the temporary objects but still is not the best
that can be done to restrict the writing of non-sense which is quite
easily and often unforgiving with C++:
>
SomeType t, u;
...
(t = u).DoStuff();

Firstly, you started from an attack on "temporaries", and then suddenly
switched to this code, where there are no temporaries. Where's the
connection? Secondly, there's absolutely no "nonsense" in this code.
Some people (me included) might consider it a bad style, by in any case
it is a matter of personal style.
If we write simply append a const, this will still preserve the
behaviour when we have a chain of assignments because the function
itself takes a const reference to an object of the same class, so it
matches with the return value:

class SomeType
{
const SomeType & operator=( const SomeType & lhs );
};

SomeType t, u, v;
...
t = u = v;

It still won't prevent us from invoking a const-method on the result of
the assignment

class SomeType
{
const SomeType & operator=( const SomeType & lhs );
void DoStuff() const;
};

SomeType t, u;
...
(t = u).DoStuff();

You need to decide what is it you are trying to prevent from happening
and work from there. At this time you don't seem to have a clear idea.
 
I

Ian Collins

Singulus said:
Please, elaborate.

Andrey's response says it all.

Care to elaborate on why you think one form is good and another bad in
the light of that response?
 
S

Singulus

In this case, the canonical return is "*this". It seems rather pointless
to add the const qualifier to an object that you know the caller already
has as a non-const reference to, doesn't it?

You may think "(t = u).doStuff()" is silly, but since I know that
".doStuff()" operates on the return value of op= (i.e., 't' in this
case) I find it rather a great idea. It may not seem so silly in a
different context, like this:

std::string s;
// later
s.assign(charPtr).append(otherCharPtr);

I often set-up method chains
(http://www.parashift.com/c++-faq-lite/references.html#faq-8.4). It's
rather standard in SmallTalk as well (in SmallTalk, if a method doesn't
explicitly return something else, it will return self (this).)

Since when method chaining is a good practice in C++? The whole point
of returning reference to this in the copy assignment operator is to
enable chaining of assignments, not to stack another thing to do
(member function calls for example). The const qualifier for the
return reference doen't solve the whole problem of calling member
functions on the result, but since of these 'side effects' or
'chaining' code uses non-const member functions, it will solve the
most frequent case of abuse.

std::string s;
// later
s.assign(charPtr).append(otherCharPtr); // These are all non-const
functions.

// NOW that's is silly...the line does more than one thing.
size_t size = (s = y).GetSize();
 
A

Andrey Tarasevich

Singulus said:
Since when method chaining is a good practice in C++?

Since forever, of course. "Method chaining", just like everything in
C++, is a technique that can be used correctly or incorrectly. When used
correctly, it is a good practice.
The whole point
of returning reference to this in the copy assignment operator is to
enable chaining of assignments, not to stack another thing to do
(member function calls for example). The const qualifier for the
return reference doen't solve the whole problem of calling member
functions on the result, but since of these 'side effects' or
'chaining' code uses non-const member functions, it will solve the
most frequent case of abuse.

std::string s;
// later
s.assign(charPtr).append(otherCharPtr); // These are all non-const
functions.

.... and there's absolutely no "abuse" in the above example, as far as
method chaining is concerned.
// NOW that's is silly...the line does more than one thing.
size_t size = (s = y).GetSize();

And? Obviously 'const' will not help here anyway.

So, you seem to be campaigning against "doing more than one thing in one
line"?.. While thoughtlessly doing too many things "in one line" might
easily result in ugly and silly code, in general case almost every
"line" in C++ does "more than one thing", if you look at it carefully.
Which is why such an unconditional disdain to "more than one thing in
one line" as the one you seem to be demonstrating will not likely result
in anything constructive.
 
P

peter koch

Introduction:

It is good advice to write the return type of a function that returns
by value as a const:

SomeType GetSomeType(...); // BAD
const SomeType GetSomeType(...); // GOOD

The above is good because it prevents the function return value to be
used as a temporary:

GetSomeType().DoStuff();

This is especially useful in the case of operator functions that
creates objects, for example:

Vector3 operator+( const Vector & lhs, const Vector & rhs ); // BAD
const Vector3 operator+( const Vector & lhs, const Vector & rhs ); //
GOOD
(vec1 + vec2).Normalize(); // BAD variant pass this non-nonse, GOOD
stops it at compile time

The Question:

Why the same principle is not used for the copy assignment of classes?

Currently, the canonical copy assignment operator function prototype
is written like this:

class SomeType
{
    SomeType & operator=( const SomeType & lhs );

};

This will pass a non-sense code like the following. The non-sense is
not the same as with the temporary objects but still is not the best
that can be done to restrict the writing of non-sense which is quite
easily and often unforgiving with C++:

SomeType t, u;
...
(t = u).DoStuff();

If we write simply append a const, this will still preserve the
behaviour when we have a chain of assignments because the function
itself takes a const reference to an object of the same class, so it
matches with the return value:

class SomeType
{
    const SomeType & operator=( const SomeType & lhs );

};

SomeType t, u, v;
...
t = u = v;
 
E

Erik Wikström

Since when method chaining is a good practice in C++?

Since when is it a bad practice? Operator overloading can help us hide
the fact that chaining is taking place but it's one of the prime
examples of when to use it.

Sure, there are times when it is bad style to use chaining, like some of
the cases you have shown, but I prefer consistency over special rules
(i.e. I prefer to always allow chaining, even if it allows some bad
style), than to have some cases where it is not possible).
 
A

Andrew Koenig

Is this a hard and fast rule? No. I don't expect you'll get any
grief or find any dangerous dragons down the corridor for returning
const ref.

Not true. If you define a class with a copy assignment operator that
returns a const ref, your class does not meet the requirements for an
element type for an STL container. So...

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

std::vector<Mine> v; // Undefined behavior -- Mine does not meet
the requirements for std::vector
 
D

Daniel T.

Since when method chaining is a good practice in C++?

Why would you think it isn't?
The whole point
of returning reference to this in the copy assignment operator is to
enable chaining of assignments, not to stack another thing to do
(member function calls for example).

Not really. The whole "point" of returning a reference (if you can
call it that) is to more closely mimic the behavior of C. I guess it
can be argued that mimicing C behavior isn't a good thing though.
// NOW that's is silly...the line does more than one thing.
size_t size = (s = y).GetSize();

In that case:

class Foo {
public:
void operator=(const Foo& f);
};

Is probably the best thing to do.
 
A

Andrey Tarasevich

Daniel said:
Now I'm confused. As I understand it, the language doesn't require op=
to return any particular type. Stroustrup in "TC++PL" often declares it
to return void...

It is not about language in general. It is about standard containers
specifically. Standard containers library has its own requirements.
Container elements are required to be 'Assignable'. And part of the
'Assignable' requirement is that assignment expression must return a
non-constant reference (23.1/4).

If you are not planning to use your class as a container element, you
can declare your copy-assignment operator to return whatever you please,
including 'void'. But with standard containers your hands are tied.
 
J

joseph cook

It is not about language in general. It is about standard containers
specifically. Standard containers library has its own requirements.
Container elements are required to be 'Assignable'. And part of the
'Assignable' requirement is that assignment expression must return a
non-constant reference (23.1/4).

If you are not planning to use your class as a container element, you
can declare your copy-assignment operator to return whatever you please,
including 'void'. But with standard containers your hands are tied.

Yes, I'm familiar with that Assignable requirement referenced in that
section of the standard, I just have never run across a reason _why_
returning a const ref would be improper. (i.e. what _might_ a
compiler implementation do based on this requirement that would
otherwise be invalid).

I wonder if gcc should fail when I violate this with -
D_GLIBCXX_CONCEPT_CHECKS (it doesn't).

Joe Cook
 
K

Kai-Uwe Bux

Daniel said:
Now I'm confused. As I understand it, the language doesn't require op=
to return any particular type. Stroustrup in "TC++PL" often declares it
to return void...

It's not the language but the Assignable requirement from containers. Table
64 from [23.1./4] requires the return type of t = u to be T&.


Best

Kai-Uwe Bux
 
J

James Kanze

On Nov 23, 9:12 pm, Andrey Tarasevich
Yes, I'm familiar with that Assignable requirement referenced
in that section of the standard, I just have never run across
a reason _why_ returning a const ref would be improper. (i.e.
what _might_ a compiler implementation do based on this
requirement that would otherwise be invalid).

An implementation could, somewhere in its internal code, do
something like:

T&
Container<T,...>::someFunction(
T const& newValue )
{
return myValue = newValue ;
}
I wonder if gcc should fail when I violate this with -
D_GLIBCXX_CONCEPT_CHECKS (it doesn't).

So I see. I think that you'd have to consider this a bug.
 
J

Juha Nieminen

Kai-Uwe Bux said:
Now I'm confused. As I understand it, the language doesn't require op=
to return any particular type. Stroustrup in "TC++PL" often declares it
to return void...

It's not the language but the Assignable requirement from containers. Table
64 from [23.1./4] requires the return type of t = u to be T&.

I suppose one idea behind that requirement is that you can do

a = b = c = d;

without any regard to what the type of those variables is.
 
J

James Kanze

Kai-Uwe Bux said:
Now I'm confused. As I understand it, the language doesn't
require op= to return any particular type. Stroustrup in
"TC++PL" often declares it to return void...
It's not the language but the Assignable requirement from
containers. Table 64 from [23.1./4] requires the return type
of t = u to be T&.
  I suppose one idea behind that requirement is that you can do
    a = b = c = d;
without any regard to what the type of those variables is.

That would work if the operator returned T const& as well.
 
J

Juha Nieminen

James said:
That would work if the operator returned T const& as well.

Is there any actual situation where being able to do

(a = b) = c;

is actually useful?
 
J

James Kanze

Is there any actual situation where being able to do
(a = b) = c;
is actually useful?

I'm pretty sure not. But some people might consider things
like the following useful:

(a = b) ++ ;
(a = b) += 3 ;
(a = b).append( "..." ) ;
return a = b ; // return type is T&

Personally, I don't like any of the above, and would never use
them. But other people are of a different opinion, and C++
tries to cater for all tastes. (Note that the first two result
in undefined behavior with the built-in operator=.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top