[net.lang.c] expr?

allbery@ncoast.UUCP (Brandon Allbery) (07/27/86)

Expires:

Quoted from <273@watmath.UUCP> ["Re: C Compiler bug (and fix for a different one)"], by rbutterworth@watmath.UUCP (Ray Butterworth)...
+---------------
| I'll trade for a fix for a problem with void functions and the ?: operator.
| 
| void f3(which)
| {
|     extern void f1(),f2();
|     which?f1():f2();
| }
| cc(1) gives an "incompatible types" error.
+---------------

That's not a bug, it's a feature.  Literally.

Before you start complaining, consider that the intent of functions returning
(void) is that of:

#define procedure void

procedure f1(x, y) {
...
}

++Brandon
-- 
  ---------------- /--/	Brandon S. Allbery		UUCP: decvax!cwruecmp!
 /              / /|\/	Tridelta Industries, Inc.        ncoast!tdi2!brandon
----    -------- /-++	7350 Corporate Blvd.		PHONE: +1 216 974 9210
   /   / /---,  ----	Mentor, Ohio 44060		SYSOP: UNaXcess/ncoast
  /   / /    / /  /	     -- HOME --			 (216) 781-6201 24 hrs.
 /   / /    / /  /	6615 Center St. Apt. A1-105	ARPA:  ncoast!allbery%
----  -----~ ----	Mentor, Ohio 44060-4101		 case.CSNET@csnet-relay

rgenter@BBN-LABS-B.ARPA (Rick Genter) (07/30/86)

     Before we go overboard on this issue, let's take a look at a couple of
points:

	1) C has no "function call statement".  Instead it has an "expression"
	   statement which allows for constructs such as:

		printf ("Hello world\n");
		ptr ++;
		x + 57;			/* useless, but allowed */
		blatz ();

	2) Functions may be declared to be of type (void).  For example,

		void
		blatz ()
		{
			printf ("Hello world\n");
		}

Obviously, given 1) and 2), certain exceptions relating to the use of values
of type (void) have been implemented.  Yes, you can not have:

		void	x ();
		int	y;

		y = x ();

since you are really trying to "use" a (void) value.  However, I claim that
it should be as legal to say:

		z ? x () : y ();

as it is to say:

		if ( z )
			x ();
		else	y ();

when both x and y return (void), given that C *already knows how to throw away
the value of a (void)*.
--------
Rick Genter 				BBN Laboratories Inc.
(617) 497-3848				10 Moulton St.  6/512
rgenter@labs-b.bbn.COM  (Internet new)	Cambridge, MA   02238
rgenter@bbn-labs-b.ARPA (Internet old)	linus!rgenter%BBN-LABS-B.ARPA (UUCP)

Yow!  Now I get to think about all the BAD THINGS I did to a BOWLING BALL
 when I was in JUNIOR HIGH SCHOOL!

sam@think.COM (Sam Kendall) (07/31/86)

In article <1327@ncoast.UUCP> allbery@ncoast.UUCP (Brandon Allbery) writes:
> > [Example in which expr ? f() : g() is used, where f and g return void.]
> 
> That's not a bug, it's a feature.  Literally.
> 
> Before you start complaining, consider that the intent of functions returning
> (void) is that of:
> 
> #define procedure void
> 
> procedure f1(x, y) {
> ...
> }

This is misleading.  Pascal and allied languages have a more
restrictive philosphy than C does; yet you are implying that because
procedures are allowed only as statements in Pascal, void-valued
expressions should be that way in C.  void expressions are always
allowed as the left operand of the comma operator, and as the first and
third expressions in the for statement; should be ban them there, too?
More graphically, should we cut off the hands of burglars in the U.S.
because it's done that way in Iran?

Enough cheap shots.  Here is an example of using void in "?:".  Suppose
you are writing a stdio-like package, and one function has this
specification:

	void PutC(Stream, char);

But for efficiency you want to write PutC as a macro.  So you define it
like this:

extern void _WriteBuf(Stream);
#define PutC(s, c)  (--(s)->count >= 0 ? (void) (*(s)->bufp++ = (c)) \
				       : _WriteBuf(s, c))

One can also easily come up with examples where void is useful as the
right operand of the comma operator, another place where it is illegal
in many compilers.

As far as I can figure, void is only useful in these contexts within
macros, and the reason it is useful there is that you want to make
macros syntactically expressions whenever possible, so you need to use
whatever control flow you can within expressions.

All in all, the ANSI committee did right in allowing void in these
places!

---
Sam Kendall			sam@godot.think.com
Thinking Machines Corp.		ihnp4!think!sam

jon@amdahl.UUCP (Jonathan Leech) (07/31/86)

In article <5826@think.COM>, sam@think.COM (Sam Kendall) writes:
> ...
> Enough cheap shots.  Here is an example of using void in "?:".  Suppose
> you are writing a stdio-like package, and one function has this
> specification:
> 
> 	void PutC(Stream, char);
> 
> But for efficiency you want to write PutC as a macro.  So you define it
> like this:
> 
> extern void _WriteBuf(Stream);
> #define PutC(s, c)  (--(s)->count >= 0 ? (void) (*(s)->bufp++ = (c)) \
> 				       : _WriteBuf(s, c))

    I won't argue with the desirability of being able to do this, but
I believe your example is poorly chosen. I/O functions can always fail
and some indication of this ought to be returned to the user where
possible, even though few bother to check usage like this.

    -- Jon Leech (...seismo!amdahl!jon)
    UTS Products / Amdahl Corporation
    __@/

farren@hoptoad.uucp (Mike Farren) (08/01/86)

I don't know - maybe I'm just slow.  My understanding was that the reason for
the existence of "void" was to indicate to the compiler that a function did
not return a value, therefore no provisions need be taken to supply one, 
potentially freeing up a register.  Is this wrong?  At any rate, you can
replace the "expr?(void):(void)" construction with something on the order of
"if(expr)(void); else (void);" with only a slight increase in keystrokes,
and make your compiler a lot happier.


-- 
----------------
Mike Farren
hoptoad!farren   "Tickle my funnybone!"

rbutterworth@watmath.UUCP (Ray Butterworth) (08/01/86)

> In article <5826@think.COM>, sam@think.COM (Sam Kendall) writes:
> >     void PutC(Stream, char);
> > But for efficiency you want to write PutC as a macro.  So you define it
> > like this:
> > extern void _WriteBuf(Stream);
> > #define PutC(s, c)  (--(s)->count >= 0 ? (void) (*(s)->bufp++ = (c)) \
> >                                        : _WriteBuf(s, c))
> I won't argue with the desirability of being able to do this, but
> I believe your example is poorly chosen. I/O functions can always fail
> and some indication of this ought to be returned to the user where
> possible, even though few bother to check usage like this.
> -- Jon Leech (...seismo!amdahl!jon)

But you don't know what _WriteBuf() does (neither do I).  But we
are working on a stdio-like library at waterloo which looks very
similar to this.  Checking the value returned by putchar() every
single time can be quite a waste, especially when you consider
that the only time a bad value can be returned is when the buffer
is flushed, and that only happens every 8000 characters or so
using full buffering on stdout.  It is also a pain to have to
write the code to do this check and to write whatever code is
required to handle the problem (usually print a message and exit),
especially if putchar is used in many different places in the code.
That is why "few bother to check usage like this".

The solution we use is to have the _WriteBuf function, instead of
returning an indication of error, either print the message and
exit itself, or "raise an event" that can be trapped in the user's
code (signals or longjumps).  That way users never need to check
the return status since the io function can never fail (or if it
does it doesn't return).  It makes writing correct code much simpler,
and makes previously incorrect code correct.  It also gets rid of
those thousands of useless status checks while adding no extra
overhead.  Of course this method isn't necessarily the best to use
in all cases, only in almost all cases.

rbutterworth@watmath.UUCP (Ray Butterworth) (08/02/86)

I've received mail or news from the following people, all strongly
protesting that my fix for void functions and the ?: operator was
invalid and that the current behaviour of most compilers is correct,
and that the proposed ANSI standard is as mixed up as I am.

>  From: aka@cbrma.UUCP (Andy Kashyap)
>  From: allbery@ncoast.UUCP (Brandon Allbery)
>  From: ark@alice.UucP (Andrew Koenig)
>  From: hamilton@uxc.CSO.UIUC.EDU (Wayne Hamilton)
>  From: jsdy@hadron.UUCP (Joseph S. D. Yao)
>  From: <utzoo!dciem!msb>
>  From: seismo!ll-xn!mit-amt!mit-eddie!ci-dandelion!jim (Jim Fulton)
>  From: whp@cbnap.UUCP (W. H. Pollock x4575 3S235)

First I'd like to clear up some confusion.  We seem to be arguing about
slightly different things.  In a statement such as
    i = e1 ? e2 : e3;
there are actually three distinct points where the compiler can make
checks.  At the "=", it must make sure that the types of "i" and
the result of the "?:" expression are the same or at least compatible
for an assignment.  At the "?" it must make sure that e1 is in fact
something that can be tested against 0.  At the ":" it must make sure
that the types of e2 and e3 are the same or are at least compatible.

Now, my "fix" was to the check at the ":".  Rather than allowing that
the two types may both be void perhaps it would have been better to
have done a simple test that any type is allowed as long as they are
both the same.  After all, the error message at this point does
say that the two operands of the ":" are incompatible with each other,
when in fact they are not.  For instance this fix solves the problem
of using "?:" with pointers to void functions that was reported earlier.

Even this shouldn't cause people to get upset.  The complaints were
actually about what happens (or rather doesn't happen) at the "?".
This is where the check that people want should go.  At this point
the compiler could check that the right operand (in the parse tree)
is in fact a valid expression and not void.  This is the point at
which a message such as "'?' operator returns void expression" could
be produced.  If we added that (see below) as well as leaving in
the earlier fix, it would get rid of the misleading "incompatible"
message and still produce an error that will keep these people
happy.


>  From: aka@cbrma.UUCP (Andy Kashyap)
>  A function call is an expression and can, therefore, be used anywhere an
>  expression can be used. When you declare a function (void), you state that
>  you intend to use that function as a statement instead, that you do not 
>  intend to use it in any operations. It can now only be used where a statement
>  can. Keep in mind that an expression is a statement, but NOT vice-versa.
>  
>  If you look up the reference section of K&R, somewhere it says something
>  like this (I don't have my K&R with me):
>      expression -> expression ? expression : expression
>  Thus you can not use a statement (ie (void) f1()) where an expression is
>  expected.

That did it.  You've finally convinced me of the error of my ways.
The "?:" expression has the same value and type as the two expressions
around the ":".  This type cannot be void.

I'm converted.  I've seen the light.  I'm studing the Bible much more
closely.  And look what I've found:

    comma-expression -> expression , expression
    The type and value of the result are the type and value
    of the right operand.
                                  DMR, Chapter 7, Verse 15.

But, isn't that what it says about the "?:" expression too?
Hallelujah!  I've found a sin in our compiler.  It actually
allows those evil "void expressions" on the right of a comma.
Why, this means we'll have to go back through the last few
weeks of news, take all those articles talking about void
functions and "?:" and repost them, this time changing the
"?:" to ",", since all the arguments in them hold equally
well.

In the meantime, I urge everyone (well, those mentioned above anyway),
to put the follwing fix into their compilers.  I'm sure it will find
many occurrences of this sin that may have crept into their code over
the years.


void f3(which)
{
    extern void f1(),f2();
    which?f1():f2();
}
cc(1) gives an "incompatible types" error.


In /usr/src/lib/mip/trees.c add the lines indicated by "->>".

    ...
    opact( p )  NODE *p; {
    ...
        switch( optype(o=p->in.op) ){
    ...
            case QUEST:          /* Look, the two cases are already common. */
            case COMOP:          /* This must prove the devine intent. */
->> #ifdef I_HAVE_BEEN_SAVED
->>             if (!mt2) uerror("'%s' returns void expression", opts[o]);
->> #endif
    ...
            case COLON:
                if( mt12 & MENU ) return( NCVT+PUN+PTMATCH );
                else if( mt12 & MDBI ) return( TYMATCH );
    ...
                else if( mt12 & MSTR ) return( NCVT+TYPL+OTHER );
->>             else if ( mt1==mt2 ) return TYPL;
                break;
            case ASSIGN:

greg@utcsri.UUCP (Gregory Smith) (08/03/86)

In article <1983@watmath.UUCP> rbutterworth@watmath.UUCP (Ray Butterworth) writes:
>I'm converted.  I've seen the light.  I'm studing the Bible much more
>closely.  And look what I've found:
>
>    comma-expression -> expression , expression
>    The type and value of the result are the type and value
>    of the right operand.
>                                  DMR, Chapter 7, Verse 15.
>
>But, isn't that what it says about the "?:" expression too?
>Hallelujah!  I've found a sin in our compiler.  It actually
>allows those evil "void expressions" on the right of a comma.
>Why, this means we'll have to go back through the last few
>weeks of news, take all those articles talking about void
>functions and "?:" and repost them, this time changing the
>"?:" to ",", since all the arguments in them hold equally
>well.
>
>In the meantime, I urge everyone (well, those mentioned above anyway),
>to put the follwing fix into their compilers.  I'm sure it will find
>many occurrences of this sin that may have crept into their code over
>the years.

BUT... BUT .... BUT...

you mean I can't

	void a(),b(),c();

	for( a(), b();  ... ;  a(), c() ){

????????????????????
	I can't even
		i = ( a(), b(), 2 );	/* b() on RHS of 1st , */
	???


This is getting worse! ( I strongly suspect Mr Butterworth agrees with me...)

There are two things going on in this debate:

	(1) whether the Powers That Be condone e?void:void
	(2) whether it makes sense and should be allowed.

I haven't the foggiest about (1) but I believe strongly that it *should* be
allowed. Herein is described in brief my belief system pertaining to this.
It is based on CONTEXTS, not an original idea by any means. There are
4 contexts of importance in C: The void context, the conditional context,
the value context, and the lvalue context.

	- Any expression in a void context is evaluated for
	  side-effects only.
	- Any expression in a conditional context is evaluated to
	  see if it is zero or non-zero. ( this is only significant
	  if the expression has a logical-valued operator at its top level ).
	- The value of an expression in a value context is always used.
	- There is another context, the Lvalue context, which will
	  only serve to cloud this particular issue, so I will
	  ignore it, and lump it under 'value'.

Let	EV	 = expr in void context
	EC	 = expr in cond context
	EX	 = expr in value context.

Then
	STMT	::=	 EV;
		|	while( EC ) STMT
		|	do STMT while( EC );
		|	for( EV; EC ; EV )STMT
		|	switch( EX )STMT
		|	if(EC) STMT
		|	if(EC) STMT else STMT
		|	return EX;

The above are all of the statement forms containing expressions (maybe
I missed a few ). The operators also create a particular context:

	expr ::=	EC && EC
			EC || EC
			! EC
			EC ? EE : EE		<<< looky
			EV, EE			<<< here
			< any other unary op> EX
			EX < any other binary op> EX
			EX[EX], EX(), EX++,EX--, EX(EX,..) etc etc

( here's the point ) I haven't said what EE is. Any expression in an EE
context has the same effective context of the ',' or '?:' operator containing
it.  I.e. the context is inherited. Thus:

	if( x? p: q ) i ? a : b ;

Since the first ?: is in a EC context, p and q are in conditional contexts.
Likewise 'a' and 'b' are in void contexts. The expressions e1 and e2 below
are in a value context:

	x = ( i<j? e1 : e2 );

Following this system, lint should complain about the second ?: below
but not the first:

int func(i){
	void	p(),q();

	i ? p(): q();			/* value of p or q thrown away */
	return (i==7)? p(): q();	/* value of p or q returned */
}

What really surprises me about all this... is that I always thought the
above was the Way It Is.

The e?void:void form is useful as the LHS of a comma op within an
expression ( esp with macros ). As has been pointed out so nicely by
rbutterworth, the VOID,VOID usage is very common, too.

	if( foo)	i=0, j=0;	/* i=0 and j=0 both in void context */
	j = ( i=0, x+y );		/* x + y in value context */

also, if you write (p(),q(),...) in any context, you expect to be allowed
to use a void q(), right? But q() is on the RHS of the first ,:
 <groups as>	((p(),q()),...)
So the context of q() should be inherited through the inner ',' from
the LHS of the other ',' I.e., a void context.

This is much more than a language purity point - this kind of analysis
is necessary to generate decent testing code. If 'x<y' appears in
a conditional context, then the code merely sets the condition codes.
If it is in a value context, it must give 0 or 1. ( It is usually treated
in effect as 'x<y?1:0', i.e. the exp. is forced into a conditional
context). Also, consider

	if( i? j: !k )
		foobar();
which comes out as ( VAX 4.2 BSD , no -O )

	tstl	-4(fp)	;	test i
	jeql	L9998	;	if i==0 goto L9998 { and test k }
	tstl	-8(fp)	;	test j
	jneq	L9999	;	if j != 0 goto L9999 ( and call foobar())
	jbr	L16	;	if( i true but j false ) skip it
L9998:
	tstl	-12(fp)	;	i is false. is k true?
	jneq	L16	;	skip if i false and k true.
L9999:
	calls	$0,_foobar
L16:

This is clearly optimal, and the compiler has obviously treated j and
!k in conditional contexts. Note the '9999' label numbers from the
code generator (L16 is from the parser for the 'if' ). If this system
is used, then the '!' operator will be 'free' in a conditional context
( on almost any machine ).

Lint obviously does *some* analysis of this, since it can say
"constant in conditional context". BTW, I have a program that gives
that error, but the expression is in an ARRAY DIMENSION for crissake.
		char foo[ THING1 > THING2 ? THING1 : THING2 ];
Obviously it's going to be a constant. Any lint authors listening?
Turn that off when a constant expression is called for. While I'm
wishing for things, I wanna see 'assignment in conditional context'.
We all know why by now...

So what's the story? Isn't this The Way It Is? Doesn't the above make
a whole lotta sense? Why the *^&%#^&%&^@ would e?void:void _in_a_void_
_context_ be considered linty?


-- 
"You'll need more than a Tylenol if you don't tell me where my father is!"
						- The Ice Pirates
----------------------------------------------------------------------
Greg Smith     University of Toronto      UUCP: ..utzoo!utcsri!greg

simon@cstvax.UUCP (Simon Brown) (08/04/86)

In article <1327@ncoast.UUCP> allbery@ncoast.UUCP (Brandon Allbery) writes:
>
>| void f3(which)
>| {
>|     extern void f1(),f2();
>|     which?f1():f2();
>| }
>| cc(1) gives an "incompatible types" error.
>
>That's not a bug, it's a feature.  Literally.
>

Is this the same feature that disallows

	f(which)
	{
		extern void f1(), f2();
		void (*fp)();
		fp = which? f1 : f2;
		(*fp)();
	}


which gives the same "incompatible types" error, but which can't be explained
as a confusion between functions and precedures.

-- 

			-------------------------------------------------
			| Simon Brown,	Dept. of Computer Science,	|
			|		Edinburgh University		|
			| ...!mcvax!ukc!cstvax!simon			|
			-------------------------------------------------

rbutterworth@watmath.UUCP (Ray Butterworth) (08/07/86)

> Lint obviously does *some* analysis of this, since it can say
> "constant in conditional context". BTW, I have a program that gives
> that error, but the expression is in an ARRAY DIMENSION for crissake.
>         char foo[ THING1 > THING2 ? THING1 : THING2 ];
> Obviously it's going to be a constant. Any lint authors listening?
> Turn that off when a constant expression is called for. While I'm
> wishing for things, I wanna see 'assignment in conditional context'.
> We all know why by now...


------/usr/src/lib/mip/cgram.y------(look for "con_e:")

/*    EXPRESSIONS    */
con_e:        {
#ifdef LINT
                {extern int constexp;}
                constexp=1;
#endif
                $<intval>$=instruct; stwart=instruct=0;
            } e
            %prec CM
            ={
                $$ = icons( $2 );
                instruct=$<intval>1;
#ifdef LINT
                constexp=0;
#endif
            }
        ;
.e:           e


------/usr/src/lib/mip/trees.c------(look for warning message)

    case BRANCH:
    ccwarn:
#ifdef LINT
        {extern int constexp;}
        if (hflag&&!constexp)
            werror("constant in conditional context");
#endif
    case PLUS:


------/usr/src/usr.bin/lint/lpass1.c------(add to definitions of externals)

int constexp=0;


------/usr/src/usr.bin/lint/lpass1.c------(in function contx())

contx( p, down, pl, pr ) register NODE *p; register *pl, *pr; {

    *pl = *pr = VAL;
    if (p->in.type==UNDEF) down=VAL; /* (void) cast */

    switch( p->in.op ){

    case NOT:
        *pl=down;
    case ANDAND:
    case OROR:
        if (hflag&&(p->in.right->in.op==ASSIGN))
            werror("Possible unintended assignment");
    case QUEST:
        *pr = down;
    case CBRANCH:
        if (hflag&&(p->in.left->in.op==ASSIGN))
            werror("possible unintended assignment");
        break;

    case SCONV:
    case PCONV:

henry@utzoo.UUCP (Henry Spencer) (08/08/86)

> we are working on a stdio-like library at waterloo which looks very
> similar to this.  Checking the value returned by putchar() every
> single time can be quite a waste...
> The solution we use is to have the _WriteBuf function, instead of
> returning an indication of error, either print the message and
> exit itself, or "raise an event" that can be trapped in the user's
> code (signals or longjumps).  [This wins big.]

On the whole I think this an excellent idea... but Ray, is it too late
to convince the people involved *not* to use signals for communications?
This has long been recognized as a bad idea.  And it's so unnecessary
in this case, and related ones:  it suffices to say that whenever the
_WriteBuf function fails, it calls (say) _WBerror with some suitable
parameters, and the user can substitute his own _WBerror function if he
doesn't like the default one.  This gets much of the benefit without
the various problems and unportabilities of signals.
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,decvax,pyramid}!utzoo!henry