When const doesn’t mean const

Consider this code:

procedure SplitAt(const _Input: string; _Position: integer; out _Head, _Tail: string);
begin
  _Head := Copy(_Input, 1, _Position-1);
  _Tail := Copy(_Input, _Position);
end;

It’s meant to split a given string into two parts at the given position.

Nothing fancy, really, isn’t it?

Now, consider this call to the procedure above:

var
  s1: string;
  s2: string;
begin
  s1 := 'hello world';
  SplitAt(s1, 5, s1, s2);
  WriteLn('s1: "', s1, '"');
  WriteLn('s2: "', s2, '"');
end;

Which output do you expect?

The output I got certainly wasn’t what I expected:

s1: ""
s2: ""

I took me a while to understand what happened:

It has been brought to my attention, that the following explanation is wrong:

The compiler passes the parameters right to left, so the first parameter it passes is s2, then s1, then 5 and last s1 again as the input. Since the two rightmost parameters are *out* parameters it clears them before passing them, so s1 is already an empty string when it gets passed as input.

What actually happens is that regardless of the order in which the parameters are passed clearing of the out parameters happens. This clears s1 and it doesn’t matter whether it was already passed as Input or not since strings in Delphi are always passed as pointers.

Not quite what I would have expected. It doesn’t help to remove the const modifier either.