Consider the following minimal example of method chaining, where a floating-point variable is set (using an out
parameter) by an early method and then passed (using a const
parameter) to a later method in the chain:
program ChainedConundrum;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
ValueType = Double;
TRec = record
function GetValue(out AOutput: ValueType): TRec;
procedure ShowValue(const AInput: ValueType);
end;
function TRec.GetValue(out AOutput: ValueType): TRec;
begin
AOutput := 394;
Result := Self;
end;
procedure TRec.ShowValue(const AInput: ValueType);
begin
Writeln(AInput);
end;
var
R: TRec;
Value: ValueType = 713;
begin
R.GetValue(Value).ShowValue(Value);
Readln;
end.
I initially expected this to print the floating-point number 394
(in some format), but it doesn't (necessarily); when I build the program using the 32-bit compiler of Delphi 10.3.2, the program prints 713
. Stepping through the program using the debugger confirms that the initial, pre-GetValue
value of Value
is passed to ShowValue
.
However, if I build this using the 64-bit compiler, 394
is printed. Similarly, if I change ValueType
from Double
to Int32
, I get 394
in both versions. Int64
yields 394
in 64-bit and 713
in 32-bit. Strings yield the updated value. Classes work just like records. Class methods, however, as opposed to instance methods, always give me the updated value. And, of course, abandoning method chaining (R.GetValue(Value); R.ShowValue(Value)
) does the same.
Not surprisingly, if I change the AInput
parameter of ShowValue
from a const
(or undecorated value) parameter to a var
parameter, I always get the updated value.
My conclusion is that either
My question is: which is it? And if it isn't allowed, where does the documentation state this? I have so far not been able to find the relevant passage. (The phrase "sequence point" seems very rarely to occur anywhere near the phrase "Delphi" on the WWW.)
Everyone who has commented on this issue here or elsewhere agrees that this either "feels like" or "clearly" is a compiler bug.
I've created issue RSP-29733 at the Embarcadero Jira.
Turning to possible workarounds, notice that the issue seems to be that an old value of a variable is used by the compiler. Hence, the problem arise when the value is changed close to the use of the variable.
However, the variable's address isn't changed, so if you pass the variable by reference instead of by value, the problem disappears. One way is to use a var
parameter when the value is passed the second time, even though you don't need that, or even want that semantically.
Hence, a more natural approach seems to be to use a const [Ref]
parameter:
procedure ShowValue(const [Ref] AInput: ValueType);
This has the same semantics as an undecorated const
parameter but forces the compiler to pass the variable by reference, thus avoiding the bug.