Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics not resolving method types correctly

Consider the following :

{$APPTYPE CONSOLE}

uses
  Generics.Collections;

type
  TObjProc = procedure of object;
  TFoo = class
    public procedure DoFoo;
    public procedure DoBar;
  end;

procedure TFoo.DoFoo;
begin
  WriteLn('foo');
end;

procedure TFoo.DoBar;
begin
  WriteLn('bar');
end;

var
  ProcList : TList<TObjProc>;
  Foo : TFoo;
  aProc : TObjProc;
begin
  Foo := TFoo.Create;
  ProcList := TList<TObjProc>.Create;
  ProcList.Add(Foo.DoFoo);
  ProcList.Add(Foo.DoBar);
  for aProc in ProcList do aProc;
  ReadLn;
end.

This produces the expected output of

foo
bar

Now suppose we want to assign a procedure from the list. Enumerating works, as above. This also works:

aProc := ProcList.Items[0];
aProc;

But this throws a compiler error :

aProc := ProcList.First;
// E2010 Incompatible types: 
//'procedure, untyped pointer or untyped parameter' and 'TObjProc'

Which is doubly odd since

function TList<T>.First: T;
begin
  Result := Items[0];
end;

So... what's going on?

Does this affect newer versions of Delphi as well? I'm tempted to QC this if there is a reasonable expectation that this should work (which I think there is).

like image 587
J... Avatar asked Oct 01 '14 15:10

J...


1 Answers

This is a not a compiler bug, nor indeed is this issue related to your use of generics. Both First and Last are functions so the compiler cannot tell whether you mean to call them, or reference them. Be explicit and let the compiler know that you mean to call the function by supplying parens.

aProc := ProcList.First();
aProc := ProcList.Last();

Yet again you have been caught out by the decision to allow the parens to be omitted when invoking procedures and functions. This design decision, whilst looking so appealing when it was made, is looking less so now that procedural types are so widely used in modern coding styles.

When you write ProcList.First the compiler faces an ambiguity. Do you mean to call the function, or are you wishing to refer to the function as a procedural type? In many scenarios, the compiler cannot resolve the ambiguity, but that is not the case here, where the expression is found on the right hand side of an assignment operator. Faced with this ambiguity the compiler assumes that you mean to refer to the function.

It takes this choice because the other choice would be worse. At least this way you can supply the parens and explicitly indicate that you mean for the function to be called. Were the compiler to go the other way then you'd be left looking for a way to tell it that you meant to refer to the function.

As a final aside, if First and Last had been implemented as properties there would have been no ambiguity.

like image 119
David Heffernan Avatar answered Sep 28 '22 03:09

David Heffernan