11

I have unconstrained generic type Atomic which implements an initializer (details in my previous question).

type
  Atomic<T> = class
    type TFactory = reference to function: T;
    class function Initialize(var storage: T; factory: TFactory): T;
  end;

Now I want to write simplified Initialize function which would take the type information from T (provided that typeof(T) is tkClass) and create new instance (when necessary) with the default constructor.

Sadly, this fails:

class function Atomic<T>.Initialize(var storage: T): T;
begin
  if not assigned(PPointer(@storage)^) then begin
    if PTypeInfo(TypeInfo(T))^.Kind  <> tkClass then
      raise Exception.Create('Atomic<T>.Initialize: Unsupported type');
    Result := Atomic<T>.Initialize(storage,
      function: T
      begin
        Result := TClass(T).Create; // <-- E2571
      end);
  end;
end;

Compiler reports error E2571 Type parameter 'T' doesn't have class or interface constraint.

How can I trick the compiler to create an instance of class T?

Community
  • 1
  • 1
gabr
  • 26,580
  • 9
  • 75
  • 141

3 Answers3

14

You can use GetTypeData to obtain the class reference:

Result := T(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create);

In Delphi XE2 (and hopefully in next releases), you can do:

var
  xInValue, xOutValue: TValue;

xInValue := GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create;
xInValue.TryCast(TypeInfo(T), xOutValue);
Result := xOutValue.AsType<T>;

(This rather circumvent way was discovered by used cjsalamon in the OmniThreadLibrary forum: Error in OtlSync XE2.)

gabr
  • 26,580
  • 9
  • 75
  • 141
Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
  • Provided that I cast result back to T (I've fixed your example) it works exactly as I wished - thanks! – gabr Dec 02 '11 at 12:03
  • Welcome! Yes, it's necessary to typecast. – Ondrej Kelle Dec 02 '11 at 12:04
  • It turned out (http://www.thedelphigeek.com/2011/12/creating-object-from-unconstrained.html?showComment=1323943258780#c4856703763737125607) that this code calls the wrong constructor. "Updated solution" from Linas works better in that respect. – gabr Dec 15 '11 at 10:33
8

You can use the new Delphi Rtti to make this task. The drawback of given solution is that it won't work if the constructor isn't named as Create. If you need to make it work all the time, just enumerate your type methods, check if it's a constructor and have 0 parameters and then invoke it. Works in Delphi XE. Sample code:

class function TTest.CreateInstance<T>: T;
var
  AValue: TValue;
  ctx: TRttiContext;
  rType: TRttiType;
  AMethCreate: TRttiMethod;
  instanceType: TRttiInstanceType;
begin
  ctx := TRttiContext.Create;
  rType := ctx.GetType(TypeInfo(T));
  AMethCreate := rType.GetMethod('Create');

  if Assigned(AMethCreate) and rType.IsInstance then
  begin
    instanceType := rType.AsInstance;

    AValue := AMethCreate.Invoke(instanceType.MetaclassType, []);// create parameters

    Result := AValue.AsType<T>;
  end;
end;

Updated solution:

class function TTest.CreateInstance<T>: T;
var
  AValue: TValue;
  ctx: TRttiContext;
  rType: TRttiType;
  AMethCreate: TRttiMethod;
  instanceType: TRttiInstanceType;
begin
  ctx := TRttiContext.Create;
  rType := ctx.GetType(TypeInfo(T));
  for AMethCreate in rType.GetMethods do
  begin
    if (AMethCreate.IsConstructor) and (Length(AMethCreate.GetParameters) = 0) then
    begin
      instanceType := rType.AsInstance;

      AValue := AMethCreate.Invoke(instanceType.MetaclassType, []);

      Result := AValue.AsType<T>;

      Exit;
    end;
  end;
end;

And call it like this:

var
  obj: TTestObj;
begin
  obj := TTest.CreateType<TTestObj>;
Linas
  • 5,485
  • 1
  • 25
  • 35
  • Thanks, but the core of the problem with XE2 Update 2 is that TypeInfo(T) won't compile if T is not marked with the 'class' constraint. – gabr Dec 06 '11 at 13:52
  • Didn't know this. Is this a "feature" or a bug? – Linas Dec 06 '11 at 15:26
  • I'm not sure but I'm afraid it's a feature. – gabr Dec 06 '11 at 17:09
  • It looks like my statement about TypeInfo(T) not compiling on XE2 Update 2 was wrong. Sorry :( I have updated TOndrej's answer with the solution that works on XE2 Update 2. – gabr Dec 14 '11 at 22:13
0

If I got it right, the generic type "T" is a class. In this case, just declare:

Atomic< T: class > = class

instead of the flat

Atomic< T > = class

This will tell to the compiler that T is a class type, so you'll be able to use the constructor and all the other features of class type without any extra workaround.

If my understanding was wrong in the base assumption, I apologize.

Flexo
  • 87,323
  • 22
  • 191
  • 272
nexial
  • 399
  • 1
  • 3
  • 7