Crazy idea: making postfix operators the default

Please consider the following C++ snippet:

*array[12]

Obviously, this is a dereference of the pointer stored in the 13th location in array. Or, wait, is it indexing 12 into an array pointed to by array? While this is not ambigious in the normal sense of the word (i.e. - the standard has one clear answer to which one this is), it is confusing for programmers to read and write. The root cause here is that we apply two modifiers to array, one is a prefix and the other a postfix. With no natural progression, we are left either remembering what the standard says (do you?) or employing trial and error.

The D language has already done away with this ambiguity where type are defined. The type modifiers progress linearly from left to right:

int a; // Signed 32 bit integer
int[16] b; // Array of size 16 of signed 32 bit integers
int[16]* c; // Pointer to array of size 16 of 32 bit integers.

Practical is definitely adopting that. I’ve worked with D for four years, and it was easy to get used to and far more intuitive.

While working on pointers, I started to ask myself whether it doesn’t make sense to do the same with the operators using during expression evaluations:

a // Whatever type a is
a* // Whatever a was pointing at. *a in C
a*[12] // The 13th element in the array pointed to by a

Adopting this notation will also eliminate the need for many parenthesis. For example:

a*.member // In C we'd have to write (*a).member, or a->member

I should also point out that this syntax is more intuitive to read. The transformations the variable undertakes follow one another in natural reading order, with no jumping back and forth.

Which brings us to the “why not” part of this message. The only reason not to do this, at least as far as I can think of, is that it takes getting used to.

So, what do you think? Worth the non-standardizing?

I’m putting a poll here, but please feel free to elaborate:

  • Sounds like a great idea!
  • Nooo!! I’ll never use a language that does that.
  • I’m going to write an informed opinion in a reply.

0 voters

There is a saying I subscribe to: “No one is as smart as everyone”.

The helpful internet has provided an example where such a move would be problematic:

a*-b

That could be either (a*)-b or a*(-b). Someone claimed that postfix operators that are also binary operators are a problem. I don’t subscribe to this. The problem isn’t with * being both postfix and binary. The problem is that both * and - are both postfix and binary, and that one is postfix and the other is prefix. I am yet to see a counter example.

Either way, the options I have now, as I see them, are:

  1. Give up. Revert to the C++ behavior.
  2. Find some clever way to resolve the conflict. Either define precedence and hope that the types don’t match if the programmer made a mistake, or explicitly detect this and report error. I should point out that the above is not the only case parsing breaks.
  3. Use a different operator for either dereference or multiplication, one that is not used as unary/binary.
  4. Use consistently prefix operators for both pointer resolution and for array subscripting.

Option 4 is the simplest to implement, but I think it’s horrible:

[12]*a

I think it’d be very difficult for C++ programmers to get used to that. It also runs counter to the way we think. We usually like to know what gets modified before we know how it is modified.

Personally, I’m leaning toward option #3. I’m thinking of using @ for everything unary * is used for in C++. It’s a bit of a bummer, because I’ve already had plans for @, but if we discard the history, I actually think it’s a more natural operator to use for pointers than * anyways.

So, pointer definition would be:

def var : S32@;

And dereference would be:

a@[12]

Not great, in that it would take some getting used to, but not horrible either.

As usual, comments and opinions are welcome.