Cannot compile constrained generic method

418 Views Asked by At

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

1

There are 1 best solutions below

1
On BEST ANSWER

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.