Stannum

Fixing C operators

2015-06-06

Over the years C’s expression syntax was frequently praised for its expressiveness. No wander many programming languages inherited parts of C’s grammar and its expressions, despite them being considered too cryptic for novices. However, C operator precedence and associativity rules have a bunch of historical dumbness. This would be OK on its own, but those programming languages, including C++, Java and C#, did not bother to fix any of these problems.

  • Shift operators should have the same precedence as multiplicative operators. They are essentially multiplications by a power of two. For example, it is more likely that a + 1 << b was meant to be a + 2b rather than (a + 1) 2b.

  • Bitwise and, xor and or should have precedence higher than that of comparison operators. You never need to do a bitwise and on the result of an equality check, since the later is always a Boolean, so the logical and will do. On the contrary, bit fields are frequently checked like this:

    Here a & mask and flagA | flagB should be evaluated first.

  • Assignment operators associativity is backwards. Specifically, the assignment operator is alright, its right associativity lets us write stuff like a = b = 0 to zero out a and b. Assigning b to a and then overwriting a with 0 would make no sense.

    However, this interpretation is rarely useful for the rest of the assignment operators. It is frequent to add things up. Like: a = a + b + c. In C it does not matter that much, but for C++ it is frequently more efficient to update an existing object inplace rather than constructing a temporary. Think of:

    to mean 'append b and c to a’. I’ve seen use cases for the opposite, but they are extremely rare.

  • Logical operators should return the last evaluated operand rather than a Boolean. It is a nice feature of Lua (otherwise Lua sucks BTW). It lets us write code like

    Good, but how it is going to work in a statically typed language you ask? The following list does not cover all the edge cases, but it does give a general picture of the intended behavior and the possible uses.

    • The operands should be converted to a common type by rules similar to those employed by the conditional operator, with the extra stipulation that if those rules do not apply, the resulting type is a Boolean.
    • The conversion to bool should happen before the conversion to the common type.
    • The conversion to bool should not happen on an operand that is itself a logical expression. Instead, its bool result must be reused. So that in
       f(int);
      shared_ptr s = f(0) || f(1) || f(2);]]>
      ::operator bool]]> is called at most once on each of the returned values.

Share on

← Reconstructing a height map from a normal map | Rethinking geometry libraries →