0

I know that Delphi doesn't allow these kinds of type declaration, but this is what I need:

I have a dozen of enumeration types in my code and for each one of them I have a constant array of string that maps their elements to a caption. It's like this:

type
  TMyEnum = (me0, me1, me2, me3, me4, me5, me6, me7, me8);

const
  MyEnumCaptions: array[TMyEnum] of string = ('0', '1', '2', '3', '4', '5', '6', '7', '8');

Now my goal is to implement a function that takes a set of these enumerations and returns a list of captions as a string. To prevent defining a new function per each enumeration I used generics like what David did to answer an older question of mine. This is the code:

uses
  System.SysUtils, System.TypInfo;

type
  TMyEnumSet = set of TMyEnum;

  TSet<T> = class
  strict private
    class function GetCaptionsList(PSet: PByteArray; const SizeOfSet(*in bytes*): Integer;
      const Captions: array of string): string;
    class function GetTypeInfo: PTypeInfo; inline; static;
  public
    class function IsSet: Boolean; static;
    class function CaptionList(const Value: T; const Captions: array of string): string; static;
  end;

...

{ TSet<T> }

class function TSet<T>.CaptionList(const Value: T; const Captions: array of string): string;
begin
  if not IsSet then
    raise Exception.Create('Invalid type in TSet<T>, T must be a set');

  Result := GetCaptionsList(PByteArray(@Value), SizeOf(Value), Captions);
end;

class function TSet<T>.GetCaptionsList(PSet: PByteArray; const SizeOfSet(*in bytes*): Integer;
  const Captions: array of string): string;
const
  Masks: array[0..7] of Byte = (1, 2, 4, 8, 16, 32, 64, 128);
var
  I, J: Integer;
begin
  Result := '';
  for I := 0 to SizeOfSet - 1 do
    for J := 0 to 7 do
      if (PSet^[I] and Masks[J]) > 0 then
        Result := Result + Captions[I*8+J];
end;

class function TSet<T>.IsSet: Boolean;
begin
  Result := GetTypeInfo.Kind=tkSet;
end;

class function TSet<T>.GetTypeInfo: PTypeInfo;
begin
  Result := System.TypeInfo(T);
end;

And the usage:

TSet<TMyEnumSet>.CaptionList([me1, me3, me8], MyEnumCaptions)

My problem is that I have no control on the value passed as Captions parameter. The most thing that I can do about it is to check its length like:

with GetTypeInfo^.TypeData^.CompType^.TypeData^ do
  if (Low(Captions) <> MinValue) or (High(Captions) <> MaxValue) then
    raise ...

And this is not enough since two enumerations may have same number of elements and I'll have to declare more of these enum-arrays in future. So I want something more solid like this:

TEnum<T> = class
public
  type
    S = set of T;
    A = array[T] of string;
  class function CaptionList(const ASet: S; const Captions: A): string; static;
end;

That is not allowed! But how can I achieve my goal without my problem?

saastn
  • 5,717
  • 8
  • 47
  • 78
  • 1
    Although generics do support enums (and indeed strings) they don't do it very well as they are mainly designed to support classes and records. So, I am afraid, there is no simple solution to your problem. You may be able to encapsulate in classes or records, but that is not trivial either. – Dsm Oct 06 '17 at 13:45
  • 1
    I think you've got no chance of this. The best you can do probably is a dynamic array or an open array and handle it all at runtime. – David Heffernan Oct 06 '17 at 13:53
  • @Dsm I was about to design it using classes but I just thought that it doesn't worth it. – saastn Oct 06 '17 at 14:22
  • @DavidHeffernan Is there way to check if `Captions` is of type `array[T] of ...` at runtime? If so I will remove this question and ask a new one. – saastn Oct 06 '17 at 14:26
  • 1
    You can't declare such a type at all! Your current approach is fine. You can't do better than it. – David Heffernan Oct 06 '17 at 14:39
  • 1
    Do you need captions at all? Depends on your use case of course, I'm using similar approach for registry access e.g. TRegistryKeys = (regMyKeyName), in this case since I control key names I can just use name of enumeration element as a caption – EugeneK Oct 06 '17 at 16:55
  • @EugeneK that's a considerable option for sure but I prefer not to use it. I need this for generating SQL commands where I declare an enumeration for each table and Captions are column titles. I think flexibility would be more important than convenience as the application gets more complicated. – saastn Oct 06 '17 at 21:00
  • 1
    For this case another option is to use code generation, since you already have your table definitions somewhere you can parse them on prebuild event and generate Delphi code, in this case you will remove step of manually writing all definitions – EugeneK Oct 06 '17 at 21:08

0 Answers0