Discussion:
strange overloaded operator problem
(too old to reply)
Kevin Manuele
2008-05-20 20:44:49 UTC
Permalink
This might be an IDE question, but we have a problem that is stumping us.

We have a custom Complex_ class in a library that provides basic complex
math functions. It includes some unary overloaded member operators. In the
same library are some global binary operators.

In our application, we were finding strange numerical errors, so we put the
following simple test code in the app.

Complex_ xx(2,3);
Complex_ yy(-2, -8);
Complex_ ww = xx;
ww *= yy; // member operator
ww = 0.0; // clear values
ww = xx * yy; // global binary operator


When we run it, the unary member operator gives the correct answer, but the
global operator does not. The real value is correct, but the imag is not.
We can trace into the library with the debugger, and it traces into the
constructors and assignment operators, but not into the global operator code
nor the member code. It is clearly doing some math --- but where?

Strangely, when we put the identical test code, linked to the same library,
in a simple "clean" app, it runs correctly, and we can trace into the global
operator code just as we expected.

Finally, if we create the same global operator without the 'const' args, it
works ok correctly in our app.

We have re-built our app, and all the libraries, from the ground up several
times and cannot resolve this problem.


The global code is simple, and uses the member unary operator

Complex_ operator* (const Complex_& c, const Complex_& d)
{
Complex_ x = c;
x *= d;
return x;
}


The member function is simple also:

class Complex_
{
public:
double real, imag;
...
...

Complex_& operator*= (const Complex_& c)
{
double ac = real * c.real;
double bd = imag * c.imag;
imag = ((real + imag) * (c.real + c.imag)) - ac - bd;
real = ac - bd;
return *this;
}

};


Any ideas ???

Thanks

Kevin
Chris Uzdavinis (TeamB)
2008-05-20 21:24:53 UTC
Permalink
Post by Kevin Manuele
Any ideas ???
After being burned by a library/application build error about 10 years
ago (spending at least 2 intense days looking into it), my first
reaction is to suspect that the application and library are built
differently, such that the memory layout is different between the two.
The kinds of things that might affect it are alighment, the "zero size
member space optimization" (this was my problem), treat-enums-as-int
mismatches, etc..

I'd suggest you go over every single build option in your application
and in your library, and make sure that each and every one is
compatible, especially those that could change how the object looks in
memory.

Sorry, but I don't have other ideas right now (so I hope that is it.)
--
Chris (TeamB);
Kevin Manuele
2008-05-20 21:52:58 UTC
Permalink
Good idea, and plausible.

Thanks

Kevin
Post by Chris Uzdavinis (TeamB)
Post by Kevin Manuele
Any ideas ???
After being burned by a library/application build error about 10 years
ago (spending at least 2 intense days looking into it), my first
reaction is to suspect that the application and library are built
differently, such that the memory layout is different between the two.
The kinds of things that might affect it are alighment, the "zero size
member space optimization" (this was my problem), treat-enums-as-int
mismatches, etc..
I'd suggest you go over every single build option in your application
and in your library, and make sure that each and every one is
compatible, especially those that could change how the object looks in
memory.
Sorry, but I don't have other ideas right now (so I hope that is it.)
--
Chris (TeamB);
Kevin Manuele
2008-05-21 19:04:05 UTC
Permalink
Post by Chris Uzdavinis (TeamB)
I'd suggest you go over every single build option in your application
and in your library, and make sure that each and every one is
compatible, especially those that could change how the object looks in
memory.
Went through all the libraries and the app. Found a few cases enums-as-int
and register variables were not consistent.

Re-built. Same problem.

Noted some inconsistencies in Help though:

Data Alignment defaults to Quad, but Help says Double Word is the default.


Next: rebuild the project from scratch

Thanks

Kevin
Chris Uzdavinis (TeamB)
2008-05-21 21:37:22 UTC
Permalink
Post by Kevin Manuele
Data Alignment defaults to Quad, but Help says Double Word is the default.
I think there is an explanation for this.

The compiler itself has a default setting, which you get if no
parameter is provided when invoked. Typically, this is what you get
when using the command line compiler.

The IDE uses a "dll" version of the compiler, and the IDE has its own
default values that it passes to the compiler as parameters. This is
to make the code interact with Delphi VCL code better. Different
alignment and other codegen settings.

Not all of the command line compiler defaults are the same as the IDL
defaults, so it's possible to get mismatches if you're not really
paying attention to the fine details. (Especially if the library is
built from a makefile, but your application is built using the IDE's
project manager.)
--
Chris (TeamB);
unknown
2008-05-22 22:04:24 UTC
Permalink
Post by Chris Uzdavinis (TeamB)
Post by Kevin Manuele
Data Alignment defaults to Quad, but Help says Double Word is the default.
I think there is an explanation for this.
Even simpler:
How many bytes in a Quad?
How many bytes in a Double Word?

It's a Rose thing...
Chris Uzdavinis (TeamB)
2008-05-23 14:20:22 UTC
Permalink
Post by unknown
Post by Chris Uzdavinis (TeamB)
Post by Kevin Manuele
Data Alignment defaults to Quad, but Help says Double Word is the default.
I think there is an explanation for this.
How many bytes in a Quad?
How many bytes in a Double Word?
It's a Rose thing...
Are you sure about that? I can't test this since I only have g++, but
it certainly sounds like Quad is short for Quad Word, which would be
twice that of Double Word. But if they're the same size, then you're
right, roses and all.

(But even if they are the same, it doesn't change the fact that the
command line and IDE defaults are not always the same. This is, IMHO,
a subtle yet important fact to keep in mind.)
--
Chris (TeamB);
unknown
2008-05-26 01:46:25 UTC
Permalink
Post by Chris Uzdavinis (TeamB)
Post by unknown
Post by Kevin Manuele
Data Alignment defaults to Quad, but Help says Double Word is the default.
How many bytes in a Quad?
How many bytes in a Double Word?
Are you sure about that?
Um, yeah, you're right.
BCB5 supported up to para(16) alignment, but defaulted to 4.
I see now that 2006 defaults to 8 internally (bcb32), and has
the * next to the QuadWord in Options, as well as the Help.

OP must be looking at old help, or else CG messed up the 2007 help.
Kevin Manuele
2008-05-24 18:21:28 UTC
Permalink
Post by Kevin Manuele
Next: rebuild the project from scratch
Cleared out all the CG project files. Rebuilt the project (about 85 units)
from the ground up. Error is gone. Sources not modified.

For what it's worth, this project has been modified many times, with links
to several static libs --- also modified extensively.

Thanks for all the responses, and useful discussions

Kevin
Remy Lebeau (TeamB)
2008-05-20 22:38:32 UTC
Permalink
Post by Kevin Manuele
This might be an IDE question
It is not. It is a language issue.
Post by Kevin Manuele
When we run it, the unary member operator gives the correct
answer, but the global operator does not.
Well, since the global operator calls the *= member operator, it has to
produce the same result.
Post by Kevin Manuele
Finally, if we create the same global operator without the 'const'
args, it works ok correctly in our app.
Sounds like you don't have const operators defined as class members. Can
you show what operators you actually implement in the class?


Gambit
Kevin Manuele
2008-05-20 23:11:40 UTC
Permalink
Post by Remy Lebeau (TeamB)
Sounds like you don't have const operators defined as class members. Can
you show what operators you actually implement in the class?
Gambit
Besides the one in previous post, these are class member operators. Its a
simple class, thus our frustrations. Thanks in advance for the help.


Complex_& operator= (const Complex_& c)
{
real = c.real;
imag = c.imag;
return *this;
}


Complex_& operator= (double c)
{
real = c;
imag = c;
return *this;
}

Complex_ operator- ()
{
return (Complex_(-real,-imag));
}


Complex_& operator+= (const Complex_& c)
{
real += c.real;
imag += c.imag;
return *this;
}
Thomas Maeder [TeamB]
2008-05-21 05:35:08 UTC
Permalink
Post by Kevin Manuele
Complex_& operator= (const Complex_& c)
{
real = c.real;
imag = c.imag;
return *this;
}
FWIW, this is superfluous (equivalent to what the compiler would
generate) ...
Post by Kevin Manuele
Complex_& operator= (double c)
{
real = c;
imag = c;
return *this;
}
... and this is clearly wrong (should be imag = 0;) ...
Post by Kevin Manuele
Complex_ operator- ()
{
return (Complex_(-real,-imag));
}
... and this should be const ...

... but I don't think that any of this solves your problem, since in
your testcase, the assignment from double assigns 0.0.

Or did I misunderstand something here?
Vladimir Grigoriev
2008-05-21 11:51:29 UTC
Permalink
Post by Thomas Maeder [TeamB]
Post by Kevin Manuele
Complex_& operator= (double c)
{
real = c;
imag = c;
return *this;
}
... and this is clearly wrong (should be imag = 0;) ...
This is not wrong. It is a question of realization. As the author showed
above in the code

ww = 0.0; // clear values

this operator is used to clear a Complex_ value totally. :)

Vladimir Grigoriev
Alan Bellingham
2008-05-21 12:41:00 UTC
Permalink
Post by Vladimir Grigoriev
This is not wrong. It is a question of realization. As the author showed
above in the code
ww = 0.0; // clear values
this operator is used to clear a Complex_ value totally. :)
It's clearly wrongly named, then!

I'm not quite sure what the name should be, though. Possibly
SetValueTo45DegreeVectorAtRootTowDistanceMultiplier(double value)

Alan Bellingham
--
Team Browns
ACCU Conference 2009: to be announced
Alan Bellingham
2008-05-21 12:43:41 UTC
Permalink
Post by Alan Bellingham
I'm not quite sure what the name should be, though. Possibly
SetValueTo45DegreeVectorAtRootTowDistanceMultiplier(double value)
On consideration, that's a bad name.

SetValueToPiOverFourVectorAtRootTwoDistanceMultiplier(double value) is
better.

Alan Bellingham
--
Team Browns
ACCU Conference 2009: to be announced
Vladimir Grigoriev
2008-05-21 13:24:07 UTC
Permalink
Post by Alan Bellingham
Post by Alan Bellingham
I'm not quite sure what the name should be, though. Possibly
SetValueTo45DegreeVectorAtRootTowDistanceMultiplier(double value)
On consideration, that's a bad name.
SetValueToPiOverFourVectorAtRootTwoDistanceMultiplier(double value) is
better.
Alan Bellingham
--
Team Browns
ACCU Conference 2009: to be announced
In this name nothing is said about that the operator deals with the Complex_
numbers! You should work more hardly to learn how to assign a correct names
to functions.

Vladimir Grigoriev
Vaclav Cechura
2008-05-21 13:57:48 UTC
Permalink
Post by Vladimir Grigoriev
In this name nothing is said about that the operator deals with the Complex_
numbers! You should work more hardly to learn how to assign a correct names
to functions.
Not necessary. From the first moment it is a class Complex_
member function.

Vaclav

P.S. Regarding the original discussion:

Only a few would expect Complex_ c = 2.65 to yield the complex
number (2.65; 2.65) instead of (2.65; 0.0).
Vladimir Grigoriev
2008-05-21 14:46:10 UTC
Permalink
Post by Vaclav Cechura
Only a few would expect Complex_ c = 2.65 to yield the complex
number (2.65; 2.65) instead of (2.65; 0.0).
In this case a question arises why do not use the std::complex class
template?

Vladimir Grigoriev
Kevin Manuele
2008-05-21 15:07:05 UTC
Permalink
We had serious performance issues with the std vector class (in BDS2006),
which were much slower than hand coded vectors. So we've generally stayed
away from std in cases where numeric performance is important. Don't know
if RAD2007 implementation is any better.

Thanks
Post by Vladimir Grigoriev
Post by Vaclav Cechura
Only a few would expect Complex_ c = 2.65 to yield the complex
number (2.65; 2.65) instead of (2.65; 0.0).
In this case a question arises why do not use the std::complex class
template?
Vladimir Grigoriev
Chris Uzdavinis (TeamB)
2008-05-21 15:19:01 UTC
Permalink
Post by Kevin Manuele
We had serious performance issues with the std vector class (in BDS2006),
which were much slower than hand coded vectors. So we've generally stayed
away from std in cases where numeric performance is important. Don't know
if RAD2007 implementation is any better.
I think that when they switched to the Dinkumware standard library,
the conformance went up and the performance went down. This is
largely due to Dinkum's reliance on an aggressive inliner. With lots
of small functions forwarding calls on over and over, if the inliner
doesn't eliminate them, there is an abstraction penalty.

Honestly I don't know if this is the problem or not, but it seems
reasonable, considering that this compiler's optimizer is not
particularly aggressive.
--
Chris (TeamB);
Vaclav Cechura
2008-05-22 14:17:40 UTC
Permalink
Post by Kevin Manuele
We had serious performance issues with the std vector class (in BDS2006),
which were much slower than hand coded vectors. So we've generally stayed
away from std in cases where numeric performance is important.
Did you test std::valarray instead of std::vector?

Vaclav
Ed Mulroy [TeamB]
2008-05-22 14:55:56 UTC
Permalink
Mr Cechura has suggested that you look at std::valarray. It is a good
suggestion. For a 'quicky' look at some of the things that can do, look at
this page
http://www.pixelglow.com/stories/valarray-tutorial/

Yes, that page is only an overview and refers to someone else's
implementation of valarray but it shows some rather nice aspects of what the
class will do (especially the lower half of the page).

. Ed
Post by Kevin Manuele
We had serious performance issues with the std vector class (in BDS2006),
which were much slower than hand coded vectors. So we've generally stayed
away from std in cases where numeric performance is important. Don't
know if RAD2007 implementation is any better.
Kevin Manuele
2008-05-22 23:48:19 UTC
Permalink
Post by Ed Mulroy [TeamB]
Mr Cechura has suggested that you look at std::valarray. It is a good
suggestion. For a 'quicky' look at some of the things that can do, look
at this page
http://www.pixelglow.com/stories/valarray-tutorial/
Yes, that page is only an overview and refers to someone else's
implementation of valarray but it shows some rather nice aspects of what
the class will do (especially the lower half of the page).
I'd forgotten about valarry. We'll give it a try.

Thanks to Mr. Checura and yourself.

Kevin
Kevin Manuele
2008-05-21 15:11:08 UTC
Permalink
Agree the name is vague --- since its intent is only to initialize to zero.

Thanks to all for the name suggestions, and the typing challenges :-)

Kevin
Post by Vaclav Cechura
Post by Vladimir Grigoriev
In this name nothing is said about that the operator deals with the Complex_
numbers! You should work more hardly to learn how to assign a correct names
to functions.
Not necessary. From the first moment it is a class Complex_
member function.
Vaclav
Only a few would expect Complex_ c = 2.65 to yield the complex
number (2.65; 2.65) instead of (2.65; 0.0).
Chris Uzdavinis (TeamB)
2008-05-21 14:14:52 UTC
Permalink
Post by Vladimir Grigoriev
Post by Alan Bellingham
SetValueToPiOverFourVectorAtRootTwoDistanceMultiplier(double value) is
better.
In this name nothing is said about that the operator deals with the Complex_
numbers! You should work more hardly to learn how to assign a correct names
to functions.
Do you make it a habit to include the class name in each of the
class's member functions? I think Alan's name is excellent, and I
think I'll be adding this gem to our complex number class.

;)
--
Chris (TeamB);
Vladimir Grigoriev
2008-05-21 14:53:06 UTC
Permalink
Post by Chris Uzdavinis (TeamB)
Post by Vladimir Grigoriev
Post by Alan Bellingham
SetValueToPiOverFourVectorAtRootTwoDistanceMultiplier(double value) is
better.
In this name nothing is said about that the operator deals with the Complex_
numbers! You should work more hardly to learn how to assign a correct names
to functions.
Do you make it a habit to include the class name in each of the
class's member functions? I think Alan's name is excellent, and I
think I'll be adding this gem to our complex number class.
;)
The part 'SetValue' of the name makes it difficult to understand about what
value - real or image or both - we are speaking!

Vladimir Grigoriev
Chris Uzdavinis (TeamB)
2008-05-21 14:54:09 UTC
Permalink
Post by Vladimir Grigoriev
Post by Chris Uzdavinis (TeamB)
Post by Vladimir Grigoriev
Post by Alan Bellingham
SetValueToPiOverFourVectorAtRootTwoDistanceMultiplier(double value) is
better.
In this name nothing is said about that the operator deals with the Complex_
numbers! You should work more hardly to learn how to assign a correct names
to functions.
Do you make it a habit to include the class name in each of the
class's member functions? I think Alan's name is excellent, and I
think I'll be adding this gem to our complex number class.
;)
The part 'SetValue' of the name makes it difficult to understand about what
value - real or image or both - we are speaking!
It is kind of complex, isn't it?
--
Chris (TeamB);
Alan Bellingham
2008-05-21 15:11:30 UTC
Permalink
Post by Vladimir Grigoriev
The part 'SetValue' of the name makes it difficult to understand about what
value - real or image or both - we are speaking!
But it's a complex number, so we're setting the complex value - how that
value is represented internally (real/imaginary or magnitude/vector)
should be invisible. You wouldn't want me to break encapsulation, would
you?

Anyway, adopting your suggestion could lead to a cumbersome name.

Alan Bellingham
--
Team Browns
ACCU Conference 2009: to be announced
Vladimir Grigoriev
2008-05-21 15:26:38 UTC
Permalink
Post by Alan Bellingham
Post by Vladimir Grigoriev
The part 'SetValue' of the name makes it difficult to understand about what
value - real or image or both - we are speaking!
But it's a complex number, so we're setting the complex value - how that
value is represented internally (real/imaginary or magnitude/vector)
should be invisible. You wouldn't want me to break encapsulation, would
you?
Anyway, adopting your suggestion could lead to a cumbersome name.
Real and image are not features of an implementation. They are general
characteristics of complex numbers.
More over I don't break C++ rules. I only propose to overload a function
name! The prefix SetValue was overloaded as SetRealValue and SetImagValue.

Vladimir Grigoriev
Alan Bellingham
2008-05-21 16:00:14 UTC
Permalink
Post by Vladimir Grigoriev
Real and image are not features of an implementation. They are general
characteristics of complex numbers.
But they *are* an implementation detail as far as complex numbers are
concerned. It's one of the features that makes a complex number class a
good tutorial for class design: you can use *either* real/imaginary
components internally, *or* angle/magnitude components. You're always
able to get from one to the other.

As a former engineer, I can tell you that certain fields find the polar
representation more useful than the Cartesian one.

Alan Bellingham
--
Team Browns
ACCU Conference 2009: to be announced
Bruce Salzman
2008-05-21 19:12:07 UTC
Permalink
Post by Alan Bellingham
Post by Vladimir Grigoriev
Real and image are not features of an implementation. They are general
characteristics of complex numbers.
As a former engineer, I can tell you that certain fields find the polar
representation more useful than the Cartesian one.
As a former mathematician, I also prefer the polar representation.
--
Bruce
Kevin Manuele
2008-05-21 16:17:44 UTC
Permalink
Post by Thomas Maeder [TeamB]
Post by Kevin Manuele
Complex_ operator- ()
{
return (Complex_(-real,-imag));
}
... and this should be const ...
What part should be const? We want the returned value to be alterable.

Kevin
Alex Bakaev [TeamB]
2008-05-21 17:25:21 UTC
Permalink
Post by Kevin Manuele
What part should be const? We want the returned value to be alterable.
Method itself.
Vladimir Grigoriev
2008-05-21 10:30:13 UTC
Permalink
Post by Kevin Manuele
Besides the one in previous post, these are class member operators. Its a
simple class, thus our frustrations. Thanks in advance for the help.
Complex_& operator= (const Complex_& c)
{
real = c.real;
imag = c.imag;
return *this;
}
Complex_& operator= (double c)
{
real = c;
imag = c;
return *this;
}
Complex_ operator- ()
{
return (Complex_(-real,-imag));
}
Complex_& operator+= (const Complex_& c)
{
real += c.real;
imag += c.imag;
return *this;
}
Do you have casting operators or an operator such as Complex_ * double?

Vladimir Grigoriev
Kevin Manuele
2008-05-21 15:17:02 UTC
Permalink
Not yet. Maybe when a need arises in this task.

Kevin
Post by Vladimir Grigoriev
Post by Kevin Manuele
Besides the one in previous post, these are class member operators. Its
a simple class, thus our frustrations. Thanks in advance for the help.
Complex_& operator= (const Complex_& c)
{
real = c.real;
imag = c.imag;
return *this;
}
Complex_& operator= (double c)
{
real = c;
imag = c;
return *this;
}
Complex_ operator- ()
{
return (Complex_(-real,-imag));
}
Complex_& operator+= (const Complex_& c)
{
real += c.real;
imag += c.imag;
return *this;
}
Do you have casting operators or an operator such as Complex_ * double?
Vladimir Grigoriev
Thomas Maeder [TeamB]
2008-05-21 16:00:35 UTC
Permalink
Please direct your browser at http://www.teamb.com/newsgroups and
read the newsgroup guidelines. One of them asks us not to quote entire
posts we are following up to; instead, please trim the quotes to the
parts relevant for your reply. Thanks!
Vladimir Grigoriev
2008-05-21 17:17:03 UTC
Permalink
Post by Kevin Manuele
Not yet. Maybe when a need arises in this task.
By the way, on the one hand, you have an assignment operator for assigning
a double value to a Complex_ object and, on the other hand, you have not a
multiplication operator for multiplication a Complex_ object by a double
value, have you?

An absurd idea I have is that by mistake you use a double instead of
Complex_.

For example

Complex_ c1( 10.0, 20.0 );
Complex_ c2;
double c3 = 30.0;
c 2 = c3 * c1; // c3 must be Complex_ however it is double.

The compiler does not see the operator const Complex_ operaror*( double,
const Complex_ & ); and tries to convert c1 to double (if it can do this).
After that it myltiplies two doubles and calls a constructor.

Vladimir Grigoriev
Vladimir Grigoriev
2008-05-21 17:48:27 UTC
Permalink
Post by Vladimir Grigoriev
Post by Kevin Manuele
Not yet. Maybe when a need arises in this task.
By the way, on the one hand, you have an assignment operator for assigning
a double value to a Complex_ object and, on the other hand, you have not
a multiplication operator for multiplication a Complex_ object by a double
value, have you?
An absurd idea I have is that by mistake you use a double instead of
Complex_.
For example
Complex_ c1( 10.0, 20.0 );
Complex_ c2;
double c3 = 30.0;
c 2 = c3 * c1; // c3 must be Complex_ however it is double.
The compiler does not see the operator const Complex_ operaror*( double,
const Complex_ & ); and tries to convert c1 to double (if it can do this).
After that it myltiplies two doubles and calls a constructor.
Though it could convert c3 to Complex_.. It depends on order in which the
compiler finds an appropriate function.

Vladimir Grigoriev
Kevin Manuele
2008-05-21 19:35:14 UTC
Permalink
Post by Vladimir Grigoriev
By the way, on the one hand, you have an assignment operator for assigning
a double value to a Complex_ object and, on the other hand, you have not
a multiplication operator for multiplication a Complex_ object by a double
value, have you?
Yes, we have operators for Complex_ * double, and for double * Complex_
Post by Vladimir Grigoriev
An absurd idea I have is that by mistake you use a double instead of
Complex_.
Not so absurd, but all the variables in the problem are Complex_

Thanks

Kevin
Vladimir Grigoriev
2008-05-22 10:02:27 UTC
Permalink
Post by Kevin Manuele
Post by Vladimir Grigoriev
By the way, on the one hand, you have an assignment operator for
assigning a double value to a Complex_ object and, on the other hand,
you have not a multiplication operator for multiplication a Complex_
object by a double value, have you?
Yes, we have operators for Complex_ * double, and for double * Complex_
Post by Vladimir Grigoriev
An absurd idea I have is that by mistake you use a double instead of
Complex_.
Not so absurd, but all the variables in the problem are Complex_
Thanks
Kevin
In your first post you wrote: "Finally, if we create the same global
operator without the 'const' args, it
works ok correctly in our app." This can be in case if implicit conversions
were used. If you use const args then in this case a constructor may be used
for an implicit conversion. If you use args without const specifiers then in
this case a constructor can not be used for an implicit conversion.
For example

class Complex
{
public:
double real, imag;
Complex( double theReal = 0.0, double theImag = 0.0 ): real( theReal ),
imag( theImag ) {}
Complex & operator+=( const Complex & rhs )
{
real += rhs.real;
imag += rhs.imag;
return *this;
}
};

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

int main()
{
Complex c1( 10.0, 10.0 ), c2;
double d1 = 20.0;
c2 = c1 + d1

return 0;
}

When the statement c2 = c1 + d; is executed d1 is converted to Complex with
using the constructor. However if in the operator '+' you will omit const
specifiers then the constructor cannot be used for implicit conversion
because temp values are const.
Only taking this into account I can guess why you have different results for
the operator. I think some implicit conversion is used in your code.

Vladimir Grigoriev
Vladimir Grigoriev
2008-05-22 11:16:05 UTC
Permalink
Another example of an implicit conversion.

class Complex

{

public:

double real, imag;

Complex(): real( 0 ), imag( 0 ) {}

Complex( double theReal, double theImag = 0.0 ):

real( theReal ), imag( theImag ) {}

Complex & operator+=( const Complex & rhs )

{

real += rhs.real;

imag += rhs.imag;


return *this;

}

const Complex operator+( double d )

{

real += d;

return *this;

}


operator double() const

{

return real;

}

};



const Complex operator+(const Complex& lhs, const Complex& rhs)

{

return Complex( lhs )+= rhs;

}



int main()

{

Complex c1( 10, 15 ), c2( 12, 14 );

Complex c3;


c3 = c1.operator+( c2 ); // instead of the operator Complex + Complex
the operator Complex + double will be used

std::cout << "c1.real = " << c1.real << ", c1.image = " << c1.image <<
std::endl;

std::cout << "c2.real = " << c2.real << ", c2.image = " << c2.image <<
std::endl;

std::cout << "c3.real = " << c3.real << ", c3.image = " << c3.image <<
std::endl;

return 0;

}



Vladimir Grigoriev
Vladimir Grigoriev
2008-05-22 11:29:47 UTC
Permalink
Only I made a mistake. :) Should be inside class Complex

const Complex operator+( double d )
{
return Complex( real + d );
}

Vladimir Grigoriev
Vladimir Grigoriev
2008-05-22 15:27:55 UTC
Permalink
Post by Vladimir Grigoriev
Only I made a mistake. :) Should be inside class Complex
const Complex operator+( double d )
{
return Complex( real + d );
}
Vladimir Grigoriev
And even here I made an error!

Should be

const Complex operator+( double d ) const // const was added
{
return Complex( real + d );
}

Otherwise a problem of selecting a more suitable operator+ arises for the
statement

c1 = c2 + c3; // where all variables of Complex type.

Vladimir Grigoriev
Chris Uzdavinis (TeamB)
2008-05-22 15:57:43 UTC
Permalink
Post by Vladimir Grigoriev
Post by Vladimir Grigoriev
Only I made a mistake. :) Should be inside class Complex
const Complex operator+( double d )
{
return Complex( real + d );
}
Vladimir Grigoriev
And even here I made an error!
Should be
const Complex operator+( double d ) const // const was added
{
return Complex( real + d );
}
Otherwise a problem of selecting a more suitable operator+ arises for the
statement
c1 = c2 + c3; // where all variables of Complex type.
I'd still make it pair of non-member functions, something like this:

Complex operator+(Complex const & lhs, double d)
{
return lhs.real + d;
}


Complex operator+(double d, complex const & rhs)
{
return rhs.real + d;
}

Note1: I don't like that real is public member data, but c'est la vie.

Note2: The return values of these functions are implicitly converted
to Complex because the Constructor is not explicit.


With these functions as non-members it's still reflexive, regardless
of which operands are on the left and which are on the right.
--
Chris (TeamB);
Sergiy Kanilo
2008-05-22 18:03:53 UTC
Permalink
Post by Chris Uzdavinis (TeamB)
Post by Vladimir Grigoriev
Post by Vladimir Grigoriev
Only I made a mistake. :) Should be inside class Complex
const Complex operator+( double d )
{
return Complex( real + d );
}
Vladimir Grigoriev
And even here I made an error!
Should be
const Complex operator+( double d ) const // const was added
{
return Complex( real + d );
}
[...]
Post by Chris Uzdavinis (TeamB)
Complex operator+(Complex const & lhs, double d)
{
return lhs.real + d;
}
shouldn't it be

Complex operator+(Complex const& lhs, double d)
{
return Complex( lhs.real + d, lhs.imag );
}

Cheers,
Serge
Chris Uzdavinis (TeamB)
2008-05-22 18:49:40 UTC
Permalink
Post by Sergiy Kanilo
Post by Chris Uzdavinis (TeamB)
Post by Vladimir Grigoriev
const Complex operator+( double d ) const // const was added
{
return Complex( real + d );
}
[...]
Post by Chris Uzdavinis (TeamB)
Complex operator+(Complex const & lhs, double d)
{
return lhs.real + d;
}
shouldn't it be
Complex operator+(Complex const& lhs, double d)
{
return Complex( lhs.real + d, lhs.imag );
}
...yes, but only if you want a _correct_ answer. I was mindlessly
copying the implementation from the code I was replying to. :)
--
Chris (TeamB);
Vladimir Grigoriev
2008-05-23 10:25:54 UTC
Permalink
Post by Chris Uzdavinis (TeamB)
Complex operator+(Complex const & lhs, double d)
{
return lhs.real + d;
}
Complex operator+(double d, complex const & rhs)
{
return rhs.real + d;
}
I made these operators members of the class purposely!:) I wanted to show
the different algorithms of selecting an appropriate operator for various
forms of using operators, i.e. in functional form and in infix form.

For the functional form

c3 = c1.operator+( c2 );

the compiler does not take into account the non-member operator. For the
infix form the both operators are considered by the compiler.

Vladimir Grigoriev
Chris Uzdavinis (TeamB)
2008-05-23 14:23:09 UTC
Permalink
Post by Vladimir Grigoriev
For the functional form
c3 = c1.operator+( c2 );
the compiler does not take into account the non-member operator. For the
infix form the both operators are considered by the compiler.
Of course, if you call a function using member function notation,
it'll only look for member functions...

But that doesn't address the issue of symmetry.

Complex c;
Complex c2 = 1.234 + c; // requires non-member function
--
Chris (TeamB);
Vladimir Grigoriev
2008-05-23 15:55:42 UTC
Permalink
Post by Chris Uzdavinis (TeamB)
Post by Vladimir Grigoriev
For the functional form
c3 = c1.operator+( c2 );
the compiler does not take into account the non-member operator. For the
infix form the both operators are considered by the compiler.
Of course, if you call a function using member function notation,
it'll only look for member functions...
But that doesn't address the issue of symmetry.
Complex c;
Complex c2 = 1.234 + c; // requires non-member function
I agree. As for the original post it would be good if the author showed also
a code of constructors I think using of the explicit specifier could help to
understand what is wrong.

Vladimir Grigoriev
Alan Bellingham
2008-05-22 16:15:29 UTC
Permalink
Post by Vladimir Grigoriev
const Complex operator+( double d ) const // const was added
{
return Complex( real + d );
}
Why the const value return? I've never seen it provide any value, and
unless I'd been given some justification, I would be suspicious of any
code that does it that way.

(For any binary operator, I'd follow Chris's recommendation of using a
free function anyway.)

Alan Bellingham
--
Team Browns
ACCU Conference 2009: to be announced
Vladimir Grigoriev
2008-05-23 10:42:40 UTC
Permalink
Post by Alan Bellingham
Post by Vladimir Grigoriev
const Complex operator+( double d ) const // const was added
{
return Complex( real + d );
}
Why the const value return? I've never seen it provide any value, and
unless I'd been given some justification, I would be suspicious of any
code that does it that way.
Oh, even in this simple operator I made a logical error. It would be better
if it looks like

const Complex operator+( double d ) const // const was added
{
return Complex( real + d, imag ); // imag added!
}

As for the const I'd like to point of that the sum of two values is a temp
value. Otherwise the constructions such as

c2 = ( c1 + 20.0 ) = 30.0;

will be valid.
Post by Alan Bellingham
(For any binary operator, I'd follow Chris's recommendation of using a
free function anyway.)
Please see my comments for Chris.

Vladimir Grigoriev
Kevin Manuele
2008-05-21 15:33:39 UTC
Permalink
Thanks, but not quite sure what you mean by "const operators". Can you
explain?

A (maybe related) basic language question:

If I define two operators:

ClassA operator* (const ClassA& a, const ClassA& b);
ClassA operator* (ClassA& a, ClassA& b);

The compiler doesn't complain about ambiguity.

How does the compiler know which version to call?

Thanks again

Kevin
Post by Remy Lebeau (TeamB)
Post by Kevin Manuele
This might be an IDE question
It is not. It is a language issue.
Post by Kevin Manuele
When we run it, the unary member operator gives the correct
answer, but the global operator does not.
Well, since the global operator calls the *= member operator, it has to
produce the same result.
Post by Kevin Manuele
Finally, if we create the same global operator without the 'const'
args, it works ok correctly in our app.
Sounds like you don't have const operators defined as class members. Can
you show what operators you actually implement in the class?
Gambit
Ed Mulroy [TeamB]
2008-05-21 15:50:36 UTC
Permalink
A non-const calling argument can be used as if it were a const calling
argument but the reverse is not true.

If one or both of the arguments is const then the operator*(const..., const
...) is called. Else the other is called.

. Ed
Post by Kevin Manuele
Thanks, but not quite sure what you mean by "const operators". Can you
explain?
ClassA operator* (const ClassA& a, const ClassA& b);
ClassA operator* (ClassA& a, ClassA& b);
The compiler doesn't complain about ambiguity.
How does the compiler know which version to call?
Alan Bellingham
2008-05-21 16:09:43 UTC
Permalink
Post by Kevin Manuele
ClassA operator* (const ClassA& a, const ClassA& b);
ClassA operator* (ClassA& a, ClassA& b);
The compiler doesn't complain about ambiguity.
How does the compiler know which version to call?
It will call whichever best fits the circumstances.

If *both* arguments are const, then the first is called. If neither is
const, then the second is called. If one is const, and the other is not,
then the compiler tries conversions to see what might work. Since it
*is* allowed to convert a non-const reference argument to a const one,
but not vice versa, calling the first is permitted, calling the second
is not, and there is no ambiguity after all.

If you'd defined the following only:

Complex operator*(Complex& lhs, Complex& rhs);

Complex::operator double() const;

and then attempted to multiply two Complexes, the following would
happen:

Either

a) Neither is const - no problem, it can use the Complex
operator*(Complex& lsh, Complex& rhs);

or

b) One or both are const, and it can't use the function. So it'd go off
and try to find what it *could* use.

And it would find the conversion operator (Complex::operator double()
const). So it'd convert each Complexes to a double, and multiply them
together as doubles!

(Hence Vladimir's question about whether you'd defined such a function
or not.)

In reality, you would define only

Complex operator*(Complex const& lhs, Complex const& rhs);

since your multiplication function has no reason to modify the arguments
it was called with.

Alan Bellingham
--
Team Browns
ACCU Conference 2009: to be announced
Kevin Manuele
2008-05-21 19:03:57 UTC
Permalink
Post by Alan Bellingham
It will call whichever best fits the circumstances.
My (mis)understanding of the form: func(const arg&) was that the function
could not modify argument, and that the actual const'ness of the argument
was irrelevant to the compiler.

My incomplete knowledge of this field gets revealed on daily basis :-)
Post by Alan Bellingham
And it would find the conversion operator (Complex::operator double()
const). So it'd convert each Complexes to a double, and multiply them
together as doubles!
(Hence Vladimir's question about whether you'd defined such a function
or not.)
Ah ! So Vladimir was wondering if this might be the cause of the incorrect
results. I didn't get the implication.

We don't have such a function, and not sure what purpose it would serve in
this context.

Note that (2,3) * (-2, -8) = (20, -22). Our binary code is returning
(20, -166) according to the debugger, so it appears to be doing the real
part calculation (though the debugger won't show it).

We modified the binary code to do the calculation directly, without using
the member unary function. Same incorrect result.

Thanks

Kevin
Eliot Frank
2008-05-21 20:13:31 UTC
Permalink
Post by Kevin Manuele
Note that (2,3) * (-2, -8) = (20, -22). Our binary code is returning
(20, -166) according to the debugger, so it appears to be doing the real
part calculation (though the debugger won't show it).
What if you write your result to a form control (say a TMemo) or a file?

(It's possible that the debugger is lying to you.)

-Eliot
Kevin Manuele
2008-05-22 23:43:08 UTC
Permalink
Post by Eliot Frank
(It's possible that the debugger is lying to you.)
The initial symptoms were bogus results from downstream code which uses
values from these operators. The debugger verified the incorrect input
values.

Thanks

Kevin
Vaclav Cechura
2008-05-22 14:34:39 UTC
Permalink
Post by Kevin Manuele
My (mis)understanding of the form: func(const arg&) was that the function
could not modify argument, and that the actual const'ness of the argument
was irrelevant to the compiler.
This is true unless the function is overloaded both for const
and non-const arg. The next example compiles with no errors or
warnings and fn(const) is called for a non-const argument (nt
is set to 1). If you uncomment the fn(int &) version then the
compiler prefers to call the non-const version and nt is set to
0.

//int fn(int &) { return 0; }
int fn(const int &) { return 1; }

void test
{
int nt = 22;
nt = fn(nt);
}

Vaclav
Chris Uzdavinis (TeamB)
2008-05-22 15:52:56 UTC
Permalink
Post by Kevin Manuele
Post by Alan Bellingham
It will call whichever best fits the circumstances.
My (mis)understanding of the form: func(const arg&) was that the function
could not modify argument, and that the actual const'ness of the argument
was irrelevant to the compiler.
Actually, it is very relevant: "const" is part of the type and must be
considered. The type is "reference to X" where X is "const arg".

To think of it in patterns:

/*1*/ void f(const T) {} // only *this* const is ignored
/*2*/ void f(T) {} // ERROR same declaration as above

but for whatever type T expands to, if that includes const specifiers,
THEY are crucial and cannot be ignored.

In your case, T would be "reference to const arg", so the const is
important. Or put another way, you're falling into case /*2*/ above.

Your "const" is not modifying the outermost thing. (Note: if it did,
that would mean it would be modifying the reference and falling into
category /*1*/. Also worth noting, but not completely relevant to
this discussion, const modifiers on references are not allowed anyway,
so for ALL type expressions involving references you must consider
const qualifiers as important.)
--
Chris (TeamB);
Remy Lebeau (TeamB)
2008-05-21 16:13:38 UTC
Permalink
Post by Kevin Manuele
Thanks, but not quite sure what you mean by "const operators".
Can you explain?
Declaring an operator with the 'const' keyword on the end of it indicates to
the compiler that the operator does not modify the object it is called on.
For example:

Complex_ operator- () const
{
return Complex_(-real, -imag);
}

If you try to call a non-const operator (or any method, for that matter) on
a const object, you will get a compiler warning. Having proper constness
can also help the optimizer as well.


Gambit
Loading...