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.
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
procedure SomeClass.ListTypes
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.
Something like this?
ReplyDeletefunction 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?
class procedure TUtils.EnumerateEnum(Action: TProc);
ReplyDeletetype
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;
Евгений Савин - That looks ugly but useful :)
ReplyDeleteThank you!
What is ugly? implemetation of EnumerateEnum or using of one?
ReplyDeleteWell, using can be more easy:
for Value in TUtils.EnumerateEnum do
Writeln(ToString(Value));
I would write it like this:
ReplyDeletetype
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;
Ended up with
ReplyDeletefunction 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;
...and using the above, 30 one-liner test methods, tested for various languages, set from the DUnitX TestCase attributes. Found one error :)
ReplyDeleteЕвгений Савин - The ugly part is the pointer cast, but hey - it works :)
ReplyDeleteLars Fosdal Unreusable code FTL :(
ReplyDeleteI didn't see your refactoring, Stefan Glienke. Good idea!
ReplyDelete