jpfollenius
jpfollenius

Reputation: 16620

Conditional behaviour based on concrete type for generic class

Since my question from yesterday was perhaps not completely clear and I did not get the answer I wanted, I will try to formulate it in a more general way:

Is there a way to implement special behaviour based on the actual type of an instantiated generic type either using explict conditional statements or using some kind of specialization? Pseudocode:

TGenericType <T> = class
  function Func : Integer;
end;
...
function TGenericType <T>.Func : Integer;
begin
  if (T = String) then Exit (0);
  if (T is class) then Exit (1);
end;
...
function TGenericType <T : class>.Func : Integer;
begin
Result := 1;
end;
function TGenericType <String>.Func : Integer;
begin
Result := 0;
end;

Upvotes: 16

Views: 3932

Answers (4)

Fr0sT
Fr0sT

Reputation: 3025

TypeInfo(T) is the right way. Moreover you can use all the stuff from TypInfo unit like TTypeData record to determine some specific properties of a type you use instead of generic. When you determine the the current type used instead of T, you may use pointer trick to get a value of a variable.

Here's a sample code that accepts any enumeration type as generic. Note that it will work for usual enumerations only (without fixed values like

TEnumWontWork = (first = 1, second, third)

) and the enum mustn't be declared as local type inside a procedure. In these cases compiler generates no TypeInfo for the enums.

type
  // Sample generic class that accepts any enumeration type as T
  TEnumArr<T> = class
  strict private
    fArr: array of Byte;
    fIdxType: TOrdType;
    function IdxToInt(idx: T): Int64;
    procedure Put(idx: T; Val: Byte);
    function Get(idx: T): Byte;
  public
    constructor Create;
    property Items[Index: T]: Byte read Get write Put; default;
  end;

constructor TEnumArr<T>.Create;
var
  pti: PTypeInfo;
  ptd: PTypeData;
begin
  pti := TypeInfo(T);
  if pti = nil then
    Error('no type info');
  // Perform run-time type check
  if pti^.Kind <> tkEnumeration then
    Error('not an enum');
  // Reach for TTypeData record that goes right after TTypeInfo record
  // Note that SizeOf(pti.Name) won't work here
  ptd := PTypeData(PByte(pti) + SizeOf(pti.Kind) + (Length(pti.Name)+1)*SizeOf(AnsiChar));
  // Init internal array with the max value of enumeration
  SetLength(fArr, ptd.MaxValue);
  // Save ordinal type of the enum
  fIdxType := ptd.OrdType;
end;

// Converts index given as enumeration item to integer.
// We can't just typecast here like Int64(idx) because of compiler restrictions so
//  use pointer tricks. We also check for the ordinal type of idx as it may vary
//  depending on compiler options and number of items in enumeration.
function TEnumArr<T>.IdxToInt(idx: T): Int64;
var
  p: Pointer;
begin
  p := @idx;

  case fIdxType of
    otSByte: Result := PShortInt(p)^;
    otUByte: Result := PByte(p)^;
    otSWord: Result := PSmallInt(p)^;
    otUWord: Result := PWord(p)^;
    otSLong: Result := PLongInt(p)^;
    otULong: Result := PLongWord(p)^;
  end;
end;

function TEnumArr<T>.Get(idx: T): Byte;
begin
  Result := fArr[IdxToInt(idx)];
end;

procedure TEnumArr<T>.Put(idx: T; Val: Byte);
begin
  fArr[IdxToInt(idx)] := Val;
end;

Sample of usage:

type
  TEnum  = (enOne, enTwo, enThree);
var
  tst: TEnumArr<TEnum>;
begin
  tst := TEnumArr<TEnum>.Create;
  tst[enTwo] := $FF;
  Log(tst[enTwo]);

As a resume, I used three tricks here:

1) Getting TypeInfo for T with general props of T

2) Getting TypeData for T with detailed props of T

3) Using pointer magic to get the value of parameters given as of type T.

Hope this help.

Upvotes: 2

Ruben Bartelink
Ruben Bartelink

Reputation: 61893

in C#, you can do a typeof(T) which would allow you to do something like

(T = String)

or

(T is class)

I havent seen your other question (you didnt link to it), but what are you really looking for? In general, doing something conditional on type or a typecode via ifs like you are doing or a switch is generally best transformed into having an interface or abstract function somewhere that gets customised by context.

Upvotes: 1

jpfollenius
jpfollenius

Reputation: 16620

If someone is interested how I did implement my "worst-case size with special treatment for strings"

class function RTTIUtils.GetDeepSize <T> (Variable : T) : Integer;
var
  StringLength          : Integer;
  Ptr                   : PInteger;
begin
if (TypeInfo (T) = TypeInfo (String)) then
  begin
  Ptr := @Variable;
  Ptr := PInteger (Ptr^);
  Dec (Ptr);
  StringLength := Ptr^;
  Result := StringLength * SizeOf (Char) + 12;
  end
else
  Result := 0;
end;

For me, this does the job at hand. Thanks to all contributors!

Upvotes: 4

Barry Kelly
Barry Kelly

Reputation: 42162

You can fall back to RTTI, by using TypeInfo(T) = TypeInfo(string). To test to see if something is a class, you could use something like PTypeInfo(TypeInfo(T))^.Kind = tkClass.

The PTypeInfo type and tkClass enumeration member are defined in the TypInfo unit.

Upvotes: 25

Related Questions