I want to be able to check, add and remove T:TElements from ST:TElementSet.
type
TElements = (elA, elB, elC);
TElementSet = set of TElements;
TMyClass<T, ST> = class
property SetValue:ST;
end;
Generics doesn't enable me to tell the compiler that T is an enumerated type and that ST is a set of T.
RTTI enables me to identify the types as tkEnumeration and tkSet - but I am unsure if I can make a strict connection between the two using RTTI. That doesn't really matter as I only need to twiddle the set bits by ordinal value.
The question is: Can I do this safely, using Generics and RTTI, and if so - how?
Examples and/or references to prior art would be appreciated.
Assuming that we only handle enums that are contiguous (because others don't have proper typeinfo and could not be handled so easily) we can do this simply without typeInfo/RTTI.
An enum set it just a bit mask for the elements in an enum.
So for example the set [elA, elC] equals 00000101 (right-to-left) which equals 5.
The index of the bit to set equals the ordinal value of the enum + 1 (because the first enum value has ordinal 0 but it's the 1st bit).
Since we cannot set individual bits in Delphi but only Bytes we need to calculate the correct value which leads to this code for include:
set[enum div 8] := set[enum div 8] or (1 shl (enum mod 8))
Since sets cannot contain more than 256 elements we are also save to assume that the enum value is always the size of a Byte. Handling enums that don't start at 0 would require a bit more code and reading typeinfo for their min and max value
Here some test code - I tricksed a bit with using absolute but you can also use hardcasts:
program GenericEnumSet;
{$APPTYPE CONSOLE}
type
TMyEnum = (elA, elB, elC);
TMySet = set of TMyEnum;
TEnumSet<TEnum,TSet> = record
value: TSet;
procedure Include(const value: TEnum); inline;
procedure Exclude(const value: TEnum); inline;
end;
procedure _Include(var setValue; const enumValue);
var
localEnum: Byte absolute enumValue;
localSet: array[0..31] of Byte absolute setValue;
begin
localSet[localEnum div 8] := localSet[localEnum div 8] or (1 shl (localEnum mod 8));
end;
procedure _Exclude(var setValue; const enumValue);
var
localEnum: Byte absolute enumValue;
localSet: array[0..31] of Byte absolute setValue;
begin
localSet[localEnum div 8] := localSet[localEnum div 8] and not (1 shl (localEnum mod 8));
end;
procedure TEnumSet<TEnum, TSet>.Include(const value: TEnum);
begin
_Include(Self.value, value);
end;
procedure TEnumSet<TEnum, TSet>.Exclude(const value: TEnum);
begin
_Exclude(Self.value, value);
end;
var
mySet: TEnumSet<TMyEnum,TMySet>;
myEnum: TMyEnum;
begin
mySet.value := [];
for myEnum := Low(TMyEnum) to High(TMyEnum) do
begin
mySet.Include(myEnum);
Assert(mySet.value = [Low(TMyEnum)..myEnum]);
end;
for myEnum := Low(TMyEnum) to High(TMyEnum) do
begin
mySet.Exclude(myEnum);
if myEnum < High(TMyEnum) then
Assert(mySet.value = [Succ(myEnum)..High(TMyEnum)])
else
Assert(mySet.value = []);
end;
Readln;
end.
I leave implementing other methods and error checking as an exercise for the reader.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With