Generics and enumerations

Generics and enumerations
We need a type qualifier for enumerated types that allow us to do loops and indexing with generic enumerables.

type
TMyType = (a, b, c);

TEnumToStr = reference to function(Value:T):String;

procedure SomeClass.ListTypes(ToString:TEnumToStr);
var
  v: T;
begin
  for v := Low(T) to High(T)  //<-- Error
   do Writeln(ToString(T));
...
[dcc32 Error] : E2032 For loop control variable must have ordinal type

Argh!

Any suggestion to for a clever workaround?  I need to write tests for dozens of EnumToString functions for enumerable types, and would love to avoid having to recreate the entire loop with declarations, sanity checks, logging and all.

Comments

  1. Something like this?
    function MyTypeToString(AValue: TMyType): string;
    const
      CMyTypeStrings: array[TMyType] of string = ('a', 'b', 'c');
    begin
      Result := CMyTypeStrings[AValue];
    end;

    Maybe you could use the rtti to get the information?

    ReplyDelete
  2. class procedure TUtils.EnumerateEnum(Action: TProc);
    type
      PointerOfT = ^T;
    var
      I: Integer;
      TI: PTypeInfo;
    begin
      TI := TypeInfo(T);
      Assert(TI.Kind = tkEnumeration);
      for I := TI.TypeData.MinValue to TI.TypeData.MaxValue do
        Action(PointerOfT(@I)^);
    end;

    procedure SomeClass.ListTypes(ToString:TEnumToStr);
    begin
      TUtils.EnumerateEnum(procedure Value: T
         begin
           Writeln(ToString(Value))
         end);
    end;

    ReplyDelete
  3. Евгений Савин - That looks ugly but useful :)
    Thank you!

    ReplyDelete
  4. What is ugly? implemetation of EnumerateEnum or using of one?

    Well, using can be more easy:
    for Value in TUtils.EnumerateEnum do
      Writeln(ToString(Value));

    ReplyDelete
  5. I would write it like this:

    type
      TUtils = class
        class procedure EnumerateEnum(Action: TProc);
        class function Low: Integer; inline;
        class function High: Integer; inline;
        class function Cast(const i: Integer): T; inline;
      end;

    class procedure TUtils.EnumerateEnum(Action: TProc);
    var
      I: Integer;
    begin
      for I := Low to High do
        Action(Cast(I));
    end;

    class function TUtils.High: Integer;
    begin
      Result := GetTypeData(TypeInfo(T)).MaxValue;
    end;

    class function TUtils.Low: Integer;
    begin
      Result := GetTypeData(TypeInfo(T)).MinValue;
    end;

    class function TUtils.Cast(const i: Integer): T;
    begin
      PInteger(@Result)^:= i;
    end;

    ReplyDelete
  6. Ended up with

    function TestConstantEnumToStrings.TestEnumToString(const Method: TEnumToStringFunc; Language: String): Boolean;
    type
      PointerOfT = ^T;
    var
      EnumAsString: String;
      CaseResult: Boolean;
      I: Integer;
      TI: PTypeInfo;
    begin
      Result := True;
      TI := TypeInfo(T);
      Assert.IsTrue(TI.Kind = tkEnumeration, 'Type passed is not an enumeration');
      for I := TI.TypeData.MinValue to TI.TypeData.MaxValue
      do begin
        EnumAsString := Method(PointerOfT(@I)^, Language);
        CaseResult := Pos('ToString not implemented for language', EnumAsString) <= 0;
        if not CaseResult
         then TDUnitX.CurrentRunner.Log(TLogLevel.ltError, EnumAsString);
        Result := Result and CaseResult;
      end;
      Assert.IsTrue(Result, 'Type ' +String(TI.Name) + ' is missing ToString value(s) for language "'+Language+'"');
    end;

    ReplyDelete
  7. ...and using the above, 30 one-liner test methods,  tested for various languages, set from the DUnitX TestCase attributes.  Found one error :)

    ReplyDelete
  8. Евгений Савин - The ugly part is the pointer cast, but hey - it works :)

    ReplyDelete
  9. Lars Fosdal Unreusable code FTL :(

    ReplyDelete
  10. I didn't see your refactoring, Stefan Glienke. Good idea!

    ReplyDelete

Post a Comment