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.