[comp.lang.c++] operator functions and classes

portuesi@tweezers.esd.sgi.com (Michael Portuesi) (09/25/89)

In article <1989Sep22.073138.19684@lth.se> newsuser@lth.se (LTH network news server) writes:

   We cannot define our own operator functions on
   the boolean data type, because one of the arguments must be a class object.

	   boolean operator == (boolean x, boolean y) {...}	// error

   I do not understand exactly why this restriction is required, but apparently
   it is.  Please enlighten me.


I'm pretty sure the reason why one of the arguments must be a class
object is to prevent the redefintion of operator functions for base
types such as int, float and char.

				--M

--
__
\/  Michael Portuesi	Silicon Graphics Computer Systems, Inc.
			portuesi@SGI.COM

"The best length for television programs is either 30 seconds or 8
hours." David Byrne

jima@hplsla.HP.COM (Jim Adcock) (09/27/89)

>Alan:
>If a boolean type is required, it can easily be provided in C++, with whichever
>properties are desired. Some people like, for instance to have:
>
>enum boolean { false, true, maybe };

Two problems if/when people make their own "bool" classes.  First, one
is forced to explicetely state those expressions one wants to be bool,
otherwise they will still be "int" --

	if (a&b | c&d) {dosomething();}

will still follow the built-in "int" rules of evaluation, unless one somehow
forces the compiler to recognize part of the subexpressions as "bool."  One
way to do this:

	if (bool(a&b) | bool(c&d)) {dosomething();}

Or one could declare and evaluate a, b, c, and d ahead of time:

	bool a=something, b=something, c=something, d=something;

	...

	if (a&b | c&d) {dosomething();} 

would also work.  But who would do this compared to the simplicety of "C"
"int" evaluation of boolean expressions?  Also, the poor reader of this
software is going to be darn hard pressed to discover whether "int" rules
or "bool" rules of evaluation are in effect in a given expression.  IE, the
meaning of:

if (a&b | c&d) {dosomething();}

is going to depend on whether a, b, c, d have been declared int or bool 
somewhere earlier in the program.  

Also, present generation compilers insist on making class objects 
["C" structures] memory resident, as oppose to allowing them to exist
solely in registers.  This makes a bool class several times slower than
using built-in ints in boolean expressions.  Compare the efficiency of
the int verses "bool" expressions as compiled on my m680x0 machine:

------------------------------------------------

class bool
{
  int b;
public:
  bool():b(0){}
  bool(int a):b(a){}
  friend bool operator|(const bool& a, const bool& b) {return a.b | b.b;}
  friend bool operator&(const bool& a, const bool& b) {return a.b & b.b;}
};

int intexpr(const int a, const int b, const int c, const int d)
{return (a&b) | (c&d);}

bool boolexpr(const bool& a, const bool& b, const bool& c, const bool& d)
{return (a&b) | (c&d);}

------------------------------------------------

Compiles to [AT&T 2.0, HP backend C compiler]:

------------------------------------------------

        global  intexpr(int,int,int,int) 
intexpr(int,int,int,int) :
        link.w  %a6,&0
        mov.l   8(%a6),%d0
        and.l   12(%a6),%d0
        mov.l   16(%a6),%d1
        and.l   20(%a6),%d1
        or.l    %d1,%d0
        unlk    %a6
        rts

        global  boolexpr(const bool&,const bool&,const bool&,const bool&) 
boolexpr(const bool&,const bool&,const bool&,const bool&) :
        link.w  %a6,&-40
        mov.l   %a1,-4(%a6)
        mov.l   8(%a6),-8(%a6)
        mov.l   12(%a6),-12(%a6)
        mov.l   -8(%a6),%a0
        mov.l   (%a0),%d0
        mov.l   -12(%a6),%a0
        and.l   (%a0),%d0
        mov.l   %d0,-16(%a6)
        lea     -16(%a6),%a0
        mov.l   (%a0),-32(%a6)
        mov.l   20(%a6),-(%sp)
        mov.l   16(%a6),-(%sp)
        lea     -40(%a6),%a1
        jsr     operator&(const bool&,const bool&) 
        addq.w  &8,%sp
        mov.l   %d0,%a0
        mov.l   (%a0),-36(%a6)
        mov.l   -32(%a6),%d0
        or.l    -36(%a6),%d0
        mov.l   %d0,-28(%a6)
        lea     -28(%a6),%a0
        mov.l   -4(%a6),%a1
        mov.l   (%a0),(%a1)
        mov.l   -4(%a6),%d0
        unlk    %a6
        rts

operator&(const bool&,const bool&) :
        link.w  %a6,&-8
        mov.l   %a1,-4(%a6)
        mov.l   8(%a6),%a0
        mov.l   (%a0),%d0
        mov.l   12(%a6),%a0
        and.l   (%a0),%d0
        mov.l   %d0,-8(%a6)
        lea     -8(%a6),%a0
        mov.l   (%a0),(%a1)
        mov.l   -4(%a6),%d0
        unlk    %a6
        rts

jima@hplsla.HP.COM (Jim Adcock) (10/06/89)

>>Bart Massey
>> >In article <8862@etana.tut.fi> pl@etana.tut.fi (Lehtinen Pertti) writes:
>> >| 	Then suddenly just behind the corner cames C-compiler from
>> >| 	ACME-corporation and realizes '!!a' -> negation of negation is
>> >| 	same as original -> we can optimize it away.
>> >| 
>> >| 	Nice, isn't it.  And too real too.
>> >
>> >And wrong, too.  Do you know of a compiler that does this in the
>> >general case?
>> 
>> Yes, all of them.
>
>Aiigh!  I'm sure I'm about the millionth response to this, but I just
>can't stand it anymore!
>
>	"The result of the logical negation operator ! is
>	1 if the value of its operand is 0, 0 if the value
>	of its operand is non-zero.  The type of the
>	result is int.  It is applicable to any arithmetic
>	type or to pointers"  -- K&R A:7.2, Stroustrup r.7.2
>
>	"The result of the logical negation operator ! is 0
>	if the value of its operand compares unequal to 0,
>	1 if the value of its operand compares equal to 0.
>	The result has type int.  The expression !E is
>	equivalent to (0==E)." -- ANSI X3J11/88-158 3.3.3.3 19
>
>So anything that is supposed to be a standard C or C++ compiler
>is *required* to do the right thing!  As a test, I tried 4 C compilers
>we have around:
>
>	Stanford's V6.0 68k (an ancient PCC based compiler)
>	Greenhills 68k (a modern commercial compiler)
>	BSD 4.3Tahoe VAX (a modern PCC based compiler)
>	GNU GCC1.34 VAX (a cool highly optimizing retargetable compiler)
>

I agree with Bart, add to this list the following conforming compilers 
[sorry, I don't have an apollo division compiler]

	HP-s300 C compilers
	HP-s800 C compilers
	HP-s300 C++ 2.0 compilers 
	HP-s300 g++ compilers [from FSF]
	HP-s300 gcc compilers [from FSF]

Below find another program you can use to help convince yourself that 'C' and
'C++' compilers really do evaluate '!' to a zero or one value, or alternately
produce code that works equivalent to this statement.  For example the
s300 gcc -O code produced by the following program is:

#NO_APP
gcc_compiled.:
.text
LC0:
	.ascii "compiler fails on !!-1\12\0"
LC1:
	.ascii "compiler fails on !!0\12\0"
LC2:
	.ascii "compiler fails on !!-0\12\0"
LC3:
	.ascii "compiler fails on !!1\12\0"
LC4:
	.ascii "compiler fails on !!-100\12\0"
LC5:
	.ascii "compiler fails on !!100\12\0"
LC6:
	.ascii "this compiler seems to be in conformance in its use of '!!'\12\0"
	.even
.globl _main
_main:
	link a6,#0
	pea LC6
	jbsr _printf
	unlk a6
	rts

Which clearly 'does not' 'evaluate' the multiple occurances of '!' to a 'zero
or one value' -- it doesn't 'evaluate' anything, it just figures out the
right result at compile time and generates a little code to print it.
Which is conformance.

Please warn us if anyone manages to find a compiler that isn't in conformance
as tested by the following simple program:

-------------------------------------------------------------------------------

#ifdef __cplusplus
  extern "C" {int printf(const char* const, ...);};
#endif

main()
{
  if((!!-1) != 1) {printf("compiler fails on !!-1\n");}
  else
  if((!!0)  != 0) {printf("compiler fails on !!0\n");}
  else
  if((!!-0)  != 0) {printf("compiler fails on !!-0\n");}
  else
  if((!!1)  != 1) {printf("compiler fails on !!1\n");}
  else
  if((!!-100)  != 1) {printf("compiler fails on !!-100\n");}
  else
  if((!!100)  != 1) {printf("compiler fails on !!100\n");}
  else
  printf("this compiler seems to be in conformance in its use of '!!'\n");
}

richard@pantor.UUCP (Richard Sargent) (10/07/89)

I have justed Microsoft C 5.1 on MS-DOS with Jim Adcock's example
program for verifying correct "!!" behaviour.

Both optimized and non-optimized compiles reported correct operation.

Richard Sargent                   Internet: richard@pantor.UUCP
Systems Analyst                   UUCP:     uunet!pantor!richard

jima@hplsla.HP.COM (Jim Adcock) (10/10/89)

//here's another simple test for those who complain my previous test can
//be evaluated by a compiler completely at compile time while still generating
//bogus runtime evaluations. [Obviously, no amount of testing can "prove" a 
//real compiler works right.  What I'm trying to do is convince any remaining
//sceptics who still think there is a compiler that evaluates !!x to mean x
//to find it for us! [I've yet to hear from *anyone* who actually has found
//a compiler that generates bogus code for !!x.  --Or maybe whoever wrote
//such a compiler is too ashamed to admit it! :-]]

#ifdef __cplusplus
  extern "C" 
  {
    int printf(const char* const, ...);
    int rand();	// yes I *know* rand is weak -- its also widely available.
    void exit(int status);
  };
#endif

main()
{
  int i,j,k;
  for (i=0; i<1000; ++i)
  {
    j = ((unsigned char)(rand()))-128;

    k = (j!=0);
    if (k != (!!j)) 
    {
      printf("failure on: %d\n",j);
      exit(1);
    }
    else
    {
      printf("success on: %d\n",j);
    }
  }
}

lsmith@apollo.HP.COM (Lawrence C. Smith) (10/11/89)

In article <6590293@hplsla.HP.COM> jima@hplsla.HP.COM (Jim Adcock) writes:
>//here's another simple test for those who complain my previous test can
>//be evaluated by a compiler completely at compile time while still generating
>//bogus runtime evaluations. [Obviously, no amount of testing can "prove" a 
>//real compiler works right.  What I'm trying to do is convince any remaining
>//sceptics who still think there is a compiler that evaluates !!x to mean x
>//to find it for us! [I've yet to hear from *anyone* who actually has found
>//a compiler that generates bogus code for !!x.  --Or maybe whoever wrote
>//such a compiler is too ashamed to admit it! :-]]
>

I was one of the original doubters, and I've been trying a few things
on some of the C compilers I have access to.  The results were interesting.
In general, something like:

if(!!x)...

compiles as:

if(x)

in *all* the compilers I tested.  I assumed I was vindicated in my opinion.
Then I tried a few other things:

y = !!x;

Sure enough, y was 1 when x was != 0, and 0 when it was.  The verbose version
of if:

if(!!x == 1)

compiles correctly.  This was a fascinating exercise!  It would appear
that the optimization in question was related not to expression evaluation
(which I assumed it was) but to flow control.  I have worked on lots of
compilers, but I'd be the first to admit that a C compiler was not among
them, and I more or less assumed they worked like the Pascal/Ada/Modula
family of languages as far as expression evaluation is concerned.  It appears
that, to the contrary, flow-control statements like if *do* get the optim-
ization, but expressions do not.  It is now clear that the distinction is
because of cases like !!x - optimization is obvious, yet leads to incorrect
code under certain circumstances.  In this case, whenever the result of
the expression is used as *value* and not as a *flag*.

So the majority seems to be right.  But I bet it's one royal bitch to write
an optimizing C compiler with gotchas like that running around.


Larry Smith
  Internet: lsmith@apollo.hp.com
  UUCP:     {decvax,mit-eddie,umix}!apollo!lsmith

jat@ranger.austin.ibm.com (Johnny Tamplin) (10/17/89)

If you look through the old V7 docs, there is a paper talking about PCC.  It
discusses that when evaluating an expression, it has three modes:  evaluating

a=b*3+c;
if(!a) {
	...

gets parsed as follows:
EvalSideEffect(a=b*3+c)
Assign(a,EvalValue(b*3+c))
Assign(a,b*3+c)

EvalConditional(!a,truelabel,falselabel)
EvalConditional(a,falselabel,truelabel)

Notice that the negate operator is evaluated at compile time by reversing the
sense of the test, so that in a conditional, !!a is exactly a.  If the same
expression is being evaluated for value, code will be generated for it.

It is not a lot of code to do this -- in fact, it sort of falls out rather
easily.  You have 3 big switch statements which call various other functions
to actually generate code for the operators.  I have written several "quickie"
code generators this way (although I usually add evaluate for address to make
things more straightforward), and the code isn't terribly bad without any
optimization other than peephole.  Of course, this doesn't get near the code
generated by sophisticated optimizations such as those in gcc.