Path: utzoo!attcan!utgpu!jarvis.csri.toronto.edu!mailrus!ncar!tank!uxc!iuvax!watmath!rbutterworth From: rbutterworth@watmath.waterloo.edu (Ray Butterworth) Newsgroups: comp.std.c Subject: Re: C's triadic operator. Message-ID: <26388@watmath.waterloo.edu> Date: 19 May 89 18:15:35 GMT References: <26212@watmath.waterloo.edu> <10266@smoke.BRL.MIL> Organization: U of Waterloo, Ontario Lines: 133 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; */