[comp.std.c] C's triadic operator.

rbutterworth@watmath.waterloo.edu (Ray Butterworth) (05/15/89)

The Standard has some rules about combining pointer types.

With diadic operators they are fairly obvious and reasonable:
- If one operand is a pointer and the other is the constant 0,
  the 0 will be coerced to the same type as the pointer.
- If one operand is a pointer to void, and the other is a pointer
  to some other type, the void pointer will be coerced to the
  same type as the other pointer.

With triadic operators (i.e. c?e1:e2) we have:
- If both expressions are pointers of compatible types
  the pointers will be coerced to point to the same type
  with the union of any qualifiers of the pointed at objects.
- If one expression is a pointer and the other is the constant 0,
  the 0 will be coerced to the same type as the pointer.
- If one expression is a pointer to void and the other is any
  arbitrary type, the pointer to void is coerced to that type,
  and the result will have the union of any pointed at qualifiers.

Now, all of these rules seem obvious, reasonable, and useful.

Unfortunately that last rule is not the one in the Standard.
What it has is:
- If one expression is a pointer to (possibly qualified) void
  and the other is any arbitrary type, the arbitrary pointer
  is coerced to be an unqualified pointer to void.

Does that seem obvious, reasonable, or useful to anyone out there?
The Rationale gives no justification for this behaviour.

e.g.
extern int cond1;
extern int cond2;
extern Type *ap;
extern void const volatile *vp;
extern short const *scp;
extern float volatile *fvp;

ap = cond ? scp : ( cond2 ? fvp : vp );

Now this legally assigns a value of type (pointer to one of
      constant volatile void,
      short constant,
   or float volatile )
to any arbitrary type pointer.

Not only that, as far as I know, the compiler is not even
allowed to issue any warnings about how stupid this request is.
(Of course if "vp" were replaced by "0",
 or even by "(void const volatile *)0",
 there would be lots of error messages produced.)

And for compiler implementors, this requirement creates extra
work since one can't simply pass the two pointer types to a
function that does the standard pointer type coersions.

Since the Rationale has nothing to say about this,
can someone on the Comittee tell us why such strange behaviour
with "?:" is required?

gwyn@smoke.BRL.MIL (Doug Gwyn) (05/16/89)

In article <26212@watmath.waterloo.edu> rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:
>Does that seem obvious, reasonable, or useful to anyone out there?
>The Rationale gives no justification for this behaviour.

How about:  It is always possible to validly convert a pointer of any type
to a void*.  The other direction is not always valid.

>Not only that, as far as I know, the compiler is not even
>allowed to issue any warnings about how stupid this request is.

Conforming compilers may issue warnings about anything they wish.

diamond@diamond.csl.sony.junet (Norman Diamond) (05/16/89)

In article <26212@watmath.waterloo.edu> rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:

>With diadic operators they are fairly obvious and reasonable:
...
>- If one operand is a pointer to void, and the other is a pointer
>  to some other type, the void pointer will be coerced to the
>  same type as the other pointer.

>With triadic operators (i.e. c?e1:e2) we have:
...
>- If one expression is a pointer to (possibly qualified) void
>  and the other is any arbitrary type, the arbitrary pointer
>  is coerced to be an unqualified pointer to void.

>Does that seem obvious, reasonable, or useful to anyone out there?

Not to me, though this doesn't answer your question.

>Since the Rationale has nothing to say about this,
>can someone on the Comittee tell us why such strange behaviour
>with "?:" is required?

Surely you've seen the sign, once posted at Waterloo's computer centre:

    "Consistency is the last refuge of an uncreative person."

?:-)

--
Norman Diamond, Sony Computer Science Lab (diamond%csl.sony.co.jp@relay.cs.net)
  The above opinions are my own.   |  Why are programmers criticized for
  If they're also your opinions,   |  re-implementing the wheel, when car
  you're infringing my copyright.  |  manufacturers are praised for it?

jagardner@watmath.waterloo.edu (Jim Gardner) (05/17/89)

In article <26212@watmath.waterloo.edu> rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:
>Unfortunately that last rule is not the one in the Standard.
>What it has is:
>- If one expression is a pointer to (possibly qualified) void
>  and the other is any arbitrary type, the arbitrary pointer
>  is coerced to be an unqualified pointer to void.

There is a better way to read the standard:

	(from section 3.3.15)
	"If both the second and third operands are pointers [...]
	 the result type is a pointer to a type qualified with all the type
	 qualifiers of the types pointed to by both operands. Furthermore,
	 [...]; otherwise, one operand is a pointer to void or a qualified
	 version of void, in which case the other operand is converted to
	 type pointer to void, and the result has that type."

The last part is misleading.  The otherwise clause is in a sentence that
begins with "furthermore", so the union of qualifications specified in
the first sentence also has effect in the second.  The result type is not
"pointer to void", but "pointer to void with all the type qualifiers...".

David Tanguay != Jim

rbutterworth@watmath.waterloo.edu (Ray Butterworth) (05/20/89)

Thanks to everyone that responded about my question about the
triadic operator.  I finally managed to put an answer together
from the various responses.  For those that care, it is at the
end of this.

Please note that I am not complaining about the decision
that the Committee made regarding the conditional operator.
My major gripe now is (and my original gripe was) that the
Rationale gave no indication that the Comittee was faced
with a decision as to which way to coerce the pointers,
nor any indication about why one way was chosen over the
other.  To me, that should be the main reason for the
existence of the Rationale.  Certainly it would have made
life easier for me and others, and if the Rationale had
given more details about such decisions it would probably
have reduced the load of the public reviews.

When someone in authority says "this is what you will do",
a lot of people want to question that directive.  If they
say "we've considered all the ramifications of the issue
and this is what you will do", many people will be satisfied
and have faith in the authority.  But for a lot of us
(i.e.  everyone that took part in the public review),
that isn't good enough.  We are going to ask "but did you
consider this?  or that?  and given these choices, why did
you choose the one you chose?".

For section 3.3.15, I would have liked to have seen something
like this in the Rationale:
    Since the new (void *) type is allowed to be mixed with
    other pointer types in the conditional operator, the
    Committee had to decide whether to coerce the (void *)
    pointer to have the same type as the other pointer or
    vice versa.  Coercing the (void *) pointer, as is done
    in most other mixed operations, would preserve type
    checking, but could lose some data in cases where the
    (void *) points at something less strictly aligned than
    what the other pointer points at.  Given that type
    checking is only of use in incorrect code, and that
    losing data will produce incorrect results in what
    appears to be correct code, the Committee decided
    that coercing the other pointer to have type (void *)
    is the appropriate choice.  In code where it is known
    that no data would be lost, the programmer can supply
    a cast on the (void *) pointer to make it explicitly
    the same type as the other pointer.

Instead all we got was:
    Since the result of such a conditional expression is
    void *, an approprate cast must be used.

Not only doesn't this answer the obvious question (why on
earth didn't they automatically coerce the (void *) to have the
same type as the other pointer?) it doesn't give any indication
as to why a cast "must" be used.

=============

Most of the responses I received dealt with the way I had
misinterpreted the wording of the Standard for type qualifiers.
I must say that the wording is non-obvious except to anyone
that already knows what it is trying to say.

In any case, the type qualifiers had nothing to do with what
my basic question was, so I'll ignore them in the following.


    double *dbuf;
    short *sbuf;
    char *cbuf;

    if (!dbuf)  dbuf = sbuf ? sbuf : ( cbuf ? cbuf : malloc(1000) );

Since malloc() returns (void *), sbuf and cbuf will be coerced to
(void *) and there will be no complaints about mismatched types,
even though cbuf and sbuf are probably not aligned suitably to
be used as double pointers.
What little the Rationale did say indicated that I must use a
cast.  Casting the entire expression seemed rather useless, so
obviously they meant "(double *)malloc(1000)".  That gives the
appropriate type complaints, but it certainly wasn't obvious
why I "must" supply the cast.  Wouldn't it have been a lot
easier for everyone if the conditional operator did this cast
for me (since everyone "must" supply the cast anyway)?

I now realize that when the Rationale said "must" it really meant
"might want to under certain circumstances".

But in some cases the cast is not desirable.

    void *p;
    double *dp;
    void *vp;

    p = c ? dp : vp;

Now if p is later cast to a (char *) to examine or move raw bytes,
then it is reasonable that vp might point at something that doesn't
have double alignment.  But if sizeof(dp) < sizeof(vp), any cast
or coersion of vp to have type (double *) would probably lose the
lower bits of the pointer.  Presumably this would produce incorrect
results, and so casting or coercing of vp to (double *) is not
the right thing to do.

Thus the Committee's decision to do the coersion to (void *)
instead of from (void *).

A similar situation exists with arithmetic types,
though in this case there is no way to decide which way
is the "right" way to do the coersion, so the coersion
simply follows the usual arithmetic conversions.

    long i;
    long j;
    double d;

    i = c ? j : d;

    /* is equivalent to */
    if (c)
        i = (long)(double)j;
    else
        i = d;

Now suppose that sizeof(long) = sizeof(double).
If j contains a large number, then casting it to (double) will
lose some of its least significant bits before it is recast back
to (long).

Similarly if the rules worked the other way
(i.e. coerce the double to long), then large double values
could overflow during the coersion for
    double e = c ? j : d;  /* e = (double)(long)d; */

karl@haddock.ima.isc.com (Karl Heuer) (05/23/89)

In article <26388@watmath.waterloo.edu> rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:
>[If the operands are brought to the common type (void *), then you silently
>get nonsense from the (incorrect) code]
>    if (!dbuf)  dbuf = sbuf ? sbuf : ( cbuf ? cbuf : malloc(1000) );
>[On the other hand, if the result has the non-voidptr type, then you could
>lose information from the (plausible) code]
>    vp = cond ? dp : vp;

Looks to me as though the "right" answer would have been to insist that the
operands shall have the same type%.  Let the user explicitly specify which one
is meant; this ought to reduce the "surprise factor".

Hopefully, quality implementations will have an option to warn about such
constructs.  I always use an explicit cast on type conversions anyway.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
% With the usual exception when one operand is a null pointer constant.