gorepj01
gorepj01

Reputation: 55

How to handle Sets with Generics

I am trying to convert a set of Bytes to an enumerated set using Generics. But the code does not compile. TValue.FromOrdinal(TypeInfo(T), Ord(B)).AsType does actually correctly return the enumerated value but I cannot include this value in the enumerated set.

interface 

type TByteSet = set of Byte;
type TMyNewEnum = (meZero, meOne, meTwo);
type TMyNewEnumSet = set of TMyNewEnum;

type
  TEnum<T> = class(TObject)
  public
    class function ToString(const aEnumValue: T): string; reintroduce;
    class function FromString(const aEnumString: string; const aDefault: T): T;
    class procedure FromByteSet(const Value: TByteSet; out EnumSet: TMyNewEnumSet);
  end

  implementation
Var
  MyByteSet: TMyByteSet;
  MyEnumSet: TMyNewEnumSet;
...  
class procedure TEnum<T>.FromByteSet(const Value: TByteSet; out EnumSet: TMyNewEnumSet);
var
  B: Byte;
begin
  Assert(PTypeInfo(TypeInfo(T)).Kind = tkEnumeration, 'Type parameter must be an Enumeration');
  for B in Value do
    begin
      EnumSet := EnumSet + TValue.FromOrdinal(TypeInfo(T), Ord(B)).AsType<T>; //This line does not compile
    end;
end;
...



//intended Usage
  MyByteSet := [0, 2];
  TEnum<TMyNewEnum>.FromByteSet(MyByteSet, MyEnumSet); 
  //I would like MyEnumSet to contain [meZero, meTwo]

end.

Any ideas?

Upvotes: 2

Views: 575

Answers (2)

Dsm
Dsm

Reputation: 6013

You can achieve what you want quite simply, but not the way you are trying to do it (which has been pointed out by others)

If you step through the following program you will see in the debugger that MyEnumSet ends up with the desired value.

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;


type TByteSet = set of Byte;
type TMyNewEnum = (meZero, meOne, meTwo);
type TMyNewEnumSet = set of TMyNewEnum;

type
  TEnum<T> = class(TObject)
  public
    class procedure FromByteSet(const Value: TByteSet; out EnumSet: T);
  end;

Var
  MyByteSet: TByteSet;
  MyEnumSet: TMyNewEnumSet;


procedure Test( const Parm1 : TByteSet; out Parm2 : TMyNewEnumSet );
var
  iResult : TMyNewEnumSet absolute Parm1;
begin
  Parm2 := iResult;
end;


{ TEnum<T> }

class procedure TEnum<T>.FromByteSet(const Value: TByteSet; out EnumSet : T );
var
  iResult : T absolute Value;
begin
  EnumSet := iResult;
end;

begin
  MyByteSet := [0,2];
  TEnum<TMyNewEnumSet>.FromByteSet( MyByteSet, MyEnumSet);
end.

Of course, you will need to add error checking (bounds and so on) for which you can use RTTI.

Upvotes: -1

David Heffernan
David Heffernan

Reputation: 613003

What you are attempting is not possible. For it to be possible, you would need to be able to constrain the generic type parameter to be a type over which a set can be formed. But no such generic constraint is supported by the language.

In fact your existing code already contains the tell-tale signs of the root problem. You have:

type
  TEnum<T> = class(TObject)
  public
    class procedure FromByteSet(const Value: TByteSet; out EnumSet: TMyNewEnumSet);
  end;

The elephant in the room here is that FromByteSet makes no reference to T and so is not generic.

In order to make the function generic you would need something like this:

type
  TEnum<T: record> = class(TObject)
  private
    type SetOfT = set of T;
  public
    class procedure FromByteSet(const Value: TByteSet; out EnumSet: SetOfT);
  end;

This does not compile. The compiler objects to the type declaration with:

[dcc32 Error]: E2001 Ordinal type required

That's because the compiler cannot be sure that T is an ordinal type. In order for it to do so, because T is a generic type parameter, you would need there to impose a generic constraint that T was an ordinal type. But the language supports no such constraint.

Upvotes: 2

Related Questions