
But the first principle requires that the types be compatible (i.e. there must be one-to-one correspondence between them). To see why, lets assume we can define operator== between int and float, and define it to round to the nearest int when comparing an int to a float.
No, never do that. Mixed type expressions are always coerced to the highest of the types involved or to an even higher type. Otherwise, you will really get unpleasant surprises. (OK, your code is only an illustration, I know).
You may define operator== to not round but "promote" the int to a float. That will make your equality comparison an equivalence relation. The problem then will shift to rule 3 because you cannot do the same promotion when copying. Consider this:
float a=5.1; int b=a; assert(b==a); // fails!
I don't see this as a surprise -- after all, we have performed a lossy assignment in between. Type promotion is a well understood operation.
That is, "a=b" should be defined for exactly the same types for which "a==b" is defined. Therefore, copy should only be defined between compatible images.
I like this rule. So, the fundamental question is: should a = b imply a == b?
What are the language gurus saying about this? Niklaus Wirth and Bertand Mayer are certainly in favour of the implication, whereas the C/C++ inventors opted against. In practice, it amounts to two questions:
1. Will a default implicit conversion be useful enough to tolerate its potential for surprises? 2. Can one design the system so that customized conversions can be conveniently configured, even if deep inside a nested call?
I'd like to hear the opinion of others about this.
I would say that the equality should hold if the comparison is between equal types. Comparing 5.1f with 5.1f should equal true, where comparing (float)5.1f with (double)5.1 should not be required to equal true, but still be allowed to. The problem is whether you perform an automatical upcast or downcast of one of the components. In those cases, the user isn't explicitly made aware of a change of variable and the user could expect stuff to work. I consider automatic casting ridiculous, since I prefer to define new arithmetic-style types in C++ which can't be mapped cleanly back and forth, no matter which mapping you choose. Try mapping a 32-bit int with a (32-bit) float. Comparing those two will never work properly, since neither is strictly speaking "higher" than the other. Downcasting and comparing is wrong: if (5 != 5.1f) { printf("5 equals 5.1"); } would give the unexpected result. Even upcasting and comparing is wrong, since downcasts can be implied: float f = 5.1; if (f != 5.1) { printf("something wrong"); } would print that something's wrong, which there is. Worse so, depending on the exact synthesis of output, the compiler could output that they are equal (due to caching or compile-time equality checking). There's an implicit downcast and upcast on the float, which is clearly apparent on the x86 (because it implicitly upcasts and downcasts everything to long doubles) but the problem itself is on all computers, principially. The only solution would be to disallow comparison between unequal types by default, requiring the user to specify which type of cast to use. This would fix a bunch of roundoff errors and would require the user to think about 5 == 5.1 instead of assuming it to be ok. I'm voting for the impossible, disallow comparison between unequal types without explicit casts. This can't be done since it would break about all applications written so far, including a lot of fairly trivial examples. For any new library I'm going to write that should handle arbitrary types in wrapper, I'm explicitly not going to add magic to make implicit casts work. Regards, Peter