C's triadic operator.

Ray Butterworth rbutterworth at watmath.waterloo.edu
Sat May 20 04:15:35 AEST 1989


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; */



More information about the Comp.std.c mailing list