Search code examples
delphigenericsconstraintsdelphi-10.1-berlingeneric-constraints

Cannot compile constrained generic method


Long story short: The following piece of code does not compile in Delphi 10.1 Berlin (Update 2).

interface

uses
  System.Classes, System.SysUtils;

type
  TTest = class(TObject)
  public
    function BuildComponent<T: TComponent>(const AComponentString: String): T;
  end;

  TSomeComponent = class(TComponent)
  public
    constructor Create(AOwner: TComponent; const AString: String); reintroduce;
  end;

implementation

{ TTest }

function TTest.BuildComponent<T>(const AComponentString: String): T;
begin
  if T = TSomeComponent then
    Result := TSomeComponent.Create(nil, AComponentString)
  else
    Result := T.Create(nil);
end;

{ TSomeComponent }

constructor TSomeComponent.Create(AOwner: TComponent; const AString: String);
begin
  inherited Create(AOwner);
end;

Several error messages are emitted from the compiler:

  1. E2015: Operator not applicable to this operand type

    on line if T = TSomeComponent then and

  2. E2010 Incompatible types - 'T' and 'TSomeComponent'

    on line Result := TSomeComponent.Create(nil, AComponentString).

To circumvent these, I could cast TClass(T) (for #1), as described in LU RD's answer here (despite it is said, that this bug has already been fixed in XE6), and T(TSomeComponent.Create(nil, AComponentString))(for #2). Although, I feel uncomfortable using explicit type-casting.

Is there any better way? Shouldn't the compiler recognize, that T is of type TComponent because I constrained it explicitly?


At first, I tried to declare the generic function's implementation like it's interface:

function TTest.BuildComponent<T: TComponent>(const AComponentString: String): T;

But this ended up with the error

E2029: ',', ';' or '>' expected but ':' found


Solution

  • This does not compile in any version of Delphi that I have encountered. You need to do some casting to persuade the compiler to compile this:

    function TTest.BuildComponent<T>(const AComponentString: String): T;
    begin
      if TClass(T) = TSomeComponent then
        Result := T(TSomeComponent.Create(nil, AComponentString))
      else
        Result := T(TComponentClass(T).Create(nil));
    end;
    

    That said, I think that I might prefer:

    if TClass(T).InheritsFrom(TSomeComponent) then
    

    in place of that equality test.

    Even then, trying to splice in a new constructor with different arguments, to a class based on a virtual constructor looks like a recipe for disaster to me.