Reputation: 16620
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
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
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
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
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