grr... this old nut

A

Arijit

JKop said:
Sure I can, you ain't got a C++ compiler there.

Get yourself a "C++ compiler" and compile it (or attempt to
do so), and then all will become clear.


-JKop

The compiler is perfectly alright. Temporaries are not
constant. Rather, they are rvalues.

-Arijit
 
A

Arijit

I have never understood why a return value that is of class type is modifiable
when a return value that is of primitive type is not modifiable.

<CODE>

#include <string>
using std::string;

string ReturnClassType()
{
return "";
}

int ReturnPrimitiveType()
{
return 0;
}

int main()
{
//This is okay!
ReturnClassType() = "";

//This is not okay!
ReturnPrimitiveType() = 0;

return 0;
}

</CODE>

This seems like an inconsistency to me. Both functions return temporary
objects yet one temporary is modifiable and the other temporary is not.

It seems as though one is an l-value and the other is not.

Can anyone help me to understand this?

No, both are rvalues. Built in assignment operator expects its left
hand operand to be lvalue. So compiler gives an error. But for string,
the actual call is

ReturnClassType().operator=( string("") );

Since temporaries are not const, you can invoke operator= on it.
So the code compiles.
The only way to stop it is to return const from functions.

-Arijit
 
P

Peter van Merkerk

Arijit said:
No, both are rvalues. Built in assignment operator expects its left
hand operand to be lvalue. So compiler gives an error. But for string,
the actual call is

ReturnClassType().operator=( string("") );

Since temporaries are not const, you can invoke operator= on it.
So the code compiles.
The only way to stop it is to return const from functions.

The question remains why doesn't the standard library return const objects?
 
J

JKop

Arijit posted:

No, both are rvalues. Built in assignment operator expects its left
hand operand to be lvalue. So compiler gives an error. But for string,
the actual call is

ReturnClassType().operator=( string("") );

Since temporaries are not const, you can invoke operator= on it.
So the code compiles.
The only way to stop it is to return const from functions.

-Arijit


Very interesting.


-JKop
 
T

tom_usenet

tom_usenet posted:



Okay, I'll get rid of that extra constructor (which
would'be been optimized away in anyway!) with:

C operator+(const C& lhs, const C& rhs)
{
C temp(lhs);

return temp += rhs;
}


Check.

For

C v(C() + c);

On both GCC and VC 7.1 your version does:

Default
Copy
Operator+=
Copy
Destroy
Destroy

Mine does:

Default
Operator+=
Copy
Destroy

If you pass the first parameter by reference it will always be copied
in your "C temp(lhs)" line. If you pass it by value the copy will
sometimes be optimized out (specifically when a temporary is passed).

Tom
 
J

JKop

tom_usenet posted:

If you pass the first parameter by reference it will always be copied
in your "C temp(lhs)" line. If you pass it by value the copy will
sometimes be optimized out (specifically when a temporary is passed).

Tom


But the original object in the calling function would have
to be copied before the function is called - it can't just
simply use the one from the calling function, unless
ofcourse its inline, but otherwise, you'd need a reference
to the object in the calling function.

Your code does "show" a copy constructor because the object
is copied even before the function starts.


-JKop
 
T

tom_usenet

The question remains why doesn't the standard library return const objects?

You gain no useful safety, and you lose optimization possibilities. If
you do:

C c(ReturnClassType());

If ReturnClassType returns a const object, that has to be copied into
c. If it returns a non-const object, RVO can apply and the return
value can be directly constructed inside c.

Also, there are legitimate reasons to want to call non-const functions
on return values, particularly when the non-const function returns the
original object. e.g. something like

f(get_some_string().insert(0, " extra bit"));

Obviously, you could split it into more lines, but then, if the
get_some_string() method returns a const string, an extra copy will
have to be made.

Basically, I see no point in defending yourself against errors that
you've never made!

Tom
 
T

tom_usenet

tom_usenet posted:




But the original object in the calling function would have
to be copied before the function is called - it can't just
simply use the one from the calling function, unless
ofcourse its inline, but otherwise, you'd need a reference
to the object in the calling function.

No, it *can* just use the one from the calling function - that's the
whole point of passing it by value. See 12.8/15 of the standard.
Your code does "show" a copy constructor because the object
is copied even before the function starts.

Nope, try it for yourself:

#include <iostream>

class C
{
public:
C()
{
std::cout << "Default\n";
}
C(C const&)
{
std::cout << "Copy\n";
}
~C()
{
std::cout << "Destroy\n";
}

C& operator=(C const&)
{
std::cout << "Assign\n";
}

C& operator+=(C const&)
{
std::cout << "+=\n";
return *this;
}
};

#ifdef TOM
C operator+(C lhs, const C& rhs)
{
return lhs += rhs;
}
#else
C operator+(C const& lhs, const C& rhs)
{
C temp(lhs);
return temp += rhs;
}
#endif

int main()
{
C c;
std::cout << "Test:\n";
C v(C() + c);
std::cout << "Test end\n";
}


Tom
 
J

JKop

tom_usenet posted:
No, it *can* just use the one from the calling function - that's the
whole point of passing it by value. See 12.8/15 of the standard.

If that's true, then in the *your* code, you're changing the original.

We haven't got an operator+= here, we've got an operator+.

But alas, the original remains unchanged. Why? because "lhs" is *NOT* the
original object from the calling function, it's a local non-const object
which has been copy-constructed from the supplied object in main, and as
such, you can do whatever you like with it, including an operator+=!

C operator+(C lhs, const C& rhs)
{
return lhs += rhs;
}


"lhs", before any of the body of code is executed, is copy contructed from
the object supplied in the calling function.


Let's examine the two samples. If you don't mind, I'm going to change the
name of the class, it's distracting me. *Your* sample:

Monkey operator+(Monkey lhs, const Monkey& rhs)
{
return lhs += rhs;
}


And *my* sample:

Monkey operator+(const Monkey& lhs, const Monkey& rhs)
{
Monkey temp_monkey(lhs);

return temp_monkey += rhs;
}


Before going further, my argument is *NOT* that either sample is superior,
but that *neither* is superior.

Here's some code that'll work with objects of our class:

int main()
{
Monkey pet_monkey(5); //Let's pretend it has such a constructor

Monkey wild_monkey(18);

Monkey domesticated_monkey(pet_monkey + wild_monkey);
}


When *your* code is called, a Monkey object is copy-constructed from
"pet_monkey" before the body of the "operator+" function is executed. So now
you can do whatever you want with this "copy", and evidently you *are*,
you're changind it, you're performing a += on it. You argue that you're
working with the original object from the calling function; if this is so,
then the original object would be changed by the +=.
Answer: The original object isn't changed because you're not *working* with
the original object, you're working with a non-const local object in the
operator+ function.... which has been copy-constructed from the pet_monkey
object in main(). When the operator+ fuction comes to the end of its
execution, the local "copy" is destroyed.

When *my* code is called, (assuming a stack and register system), my
operator+ function will be supplied with two "hidden pointer"s. Then, I
define a local non-const object, which I copy-construct from pet_monkey.
Then I do a += on this "copy". My "copy" is destroyed when the operator+
function reaches the end of its execution.

So, with *your* code:

Copy-constructed object
operator+=
Destruction of copy-constructed object


with *my* code:

Copy-constructed object
operator+=
Destruction of copy-constructed object



-JKop
 
P

Peter van Merkerk

tom_usenet said:
You gain no useful safety, and you lose optimization possibilities. If
you do:

C c(ReturnClassType());

If ReturnClassType returns a const object, that has to be copied into
c. If it returns a non-const object, RVO can apply and the return
value can be directly constructed inside c.

Also, there are legitimate reasons to want to call non-const functions
on return values, particularly when the non-const function returns the
original object. e.g. something like

f(get_some_string().insert(0, " extra bit"));

Obviously, you could split it into more lines, but then, if the
get_some_string() method returns a const string, an extra copy will
have to be made.

Basically, I see no point in defending yourself against errors that
you've never made!

Thanks Tom, that was the kind of answer I was looking for.
 
T

tom_usenet

Thanks Tom, that was the kind of answer I was looking for.

Whoops, this bit was a lie actually:

The optimization is still allowed if ReturnClassType returns a const
C. What I actually meant to say was something about move constructors.
See here:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1610.html
Basically, a const rvalue can't be moved, whilst a non-const one can.
Making rvalues const unnecessarily just removes optimization
possibilities.

Tom
 
T

tom_usenet

tom_usenet posted:


If that's true, then in the *your* code, you're changing the original.

Yes, and since it's a temporary, that's fine.
We haven't got an operator+= here, we've got an operator+.
Indeed.

But alas, the original remains unchanged. Why? because "lhs" is *NOT* the
original object from the calling function, it's a local non-const object
which has been copy-constructed from the supplied object in main, and as
such, you can do whatever you like with it, including an operator+=!

However, if you pass a temporary, it *is* the orginal object from the
calling function.
"lhs", before any of the body of code is executed, is copy contructed from
the object supplied in the calling function.

What I'm getting very tired of telling you is that this isn't always
the case. It *isn't* copy constructed when a temporary is passed.
Let's examine the two samples. If you don't mind, I'm going to change the
name of the class, it's distracting me. *Your* sample:

Monkey operator+(Monkey lhs, const Monkey& rhs)
{
return lhs += rhs;
}


And *my* sample:

Monkey operator+(const Monkey& lhs, const Monkey& rhs)
{
Monkey temp_monkey(lhs);

return temp_monkey += rhs;
}


Before going further, my argument is *NOT* that either sample is superior,
but that *neither* is superior.

The former is superior when a temporary is passed as the first
parameter.
Here's some code that'll work with objects of our class:

int main()
{
Monkey pet_monkey(5); //Let's pretend it has such a constructor

Monkey wild_monkey(18);

Monkey domesticated_monkey(pet_monkey + wild_monkey);

That isn't my example. My example was:

Monkey domesticated_monkey(Monkey(5) + wild_monkey);

i.e. passing a temporary as the first object.
}


When *your* code is called, a Monkey object is copy-constructed from
"pet_monkey" before the body of the "operator+" function is executed.

Nope, the copy construction doesn't happen for a temporary - the
temporary is "elided".

If you don't get it by now, you never will. Did you even run my
example code?

Tom
 
T

tom_usenet

tom_usenet posted:



Then you're right.


What's the name of that optimization?

I'm not sure it has an official name, but "temporary elision" seems to
fit in with the wording in the standard, or you could call it "copy
elision" or "copy constructor elision".

Tom
 
D

DaKoadMunky

No, both are rvalues.

I know the writings of bs should not be substituted for the standard, but his
glossary at http://www.research.att.com/~bs/glossary.html indicates that an
rvalue is...

"an expression that may appear on the right-hand side of an assignment, but not
of the left-hand side."

I understand that...

ReturnClassType() = ""; //1

is really just...

ReturnClassType().operator=( string("") ); //2

But 1 has to be translated to 2 at compile time. At compile time 1 has a
supposed rvalue on the left hand side of an assignment statement yet this is
not considered an error.

Is the bs definition of rvalue a little bit off with respect to the standard?

Am I making a mistake equating constancy with rvalues?
 
J

JKop

When we say that r-values are const, that temporaries are
const, we mean the following:

int Cow()
{
int monkey = 876;

return monkey;
}


int main()
{
Cow() = 46; //ERROR
}


But, if we turn that into a class:

class Blah{ };

Blah Cow()
{
Blah monkey;

return monkey;
}

int main()
{
Cow() = Blah();
}


The only explanation that can explain how an r-value is on
the left hand side of an assignment operator is as
follows... It isn't! We just have to accept that according
to the Standard, there's no assignment operator there, it's
really:

Blah::eek:perator=(const Blah&);

A plain old run-of-the-mill function - a plain old run-of-
the-mill *non-const* function, I might add.


-JKop
 
B

Bob Hairgrove

On Thu, 15 Jul 2004 17:54:03 +0100, tom_usenet

[snip]
The most efficient implementation is actually a bit weird:

C operator+ (C lhs, C const & rhs)
{
lhs += rhs;
return lhs;
}

That is potentially more efficient when a temporary is passed as the
first parameter. e.g.
C() + C();

Tom

Perhaps I'm just allergic to passing class types by value...but
doesn't it depend on what operator+= actually does?

IMHO a better design would be to factor out work done by operator+=
and operator+ into a private helper function which can be called by
both (in which case operator+ needs to be a friend). Then one could
pass the arguments by const & as is usually done.
 

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

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
SterlingLa
Top