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