Replacing overload by Generics in Delphi? - delphi

My question is very similar to 'How to use generics as a replacement for a bunch of overloaded methods working with different types?'
I tried the suggested solution GetRandomValueFromArray and it gives no complitetime errors.
But the following does not compile :
Declaration / definition in existing class Tilib ..
class function SetSingleBit<T>(const Value: T; const Bit: Byte): T;
class function Tilib.SetSingleBit<T>(const Value: T; const Bit: Byte): T;
begin
Result := Value or (1 shl Bit);
end;
Delphi 10.2 emits the error 'E2015 Operator ist auf diesen Operandentyp nicht anwendbar' (Operator not usable for the type of operand).
Does anyone know whats wrong with that ?

The problem is that T can be any type, and only certain types support the bitwise or operator.
It is possible to constrain generic types, so that the compiler knows more about their capabilities. For instance you can constrain the generic type to be a class, a sub class of a specific class, an interface, a value type, etc. But you cannot apply the constraint that you need, which would be that the type supports the bitwise or operator.
In short, the correct way to solve your problem is to use function overloading. Generics are not the solution to all problems.

Related

Delphi generic record, add constraint to Equal operator

I have the below record generic, which overrides the Equality operator.
The idea was to get some compile-time validation, as in the example below:
TpColumn<T> = record
class operator Equal (aLeft: TpColumn<T>; aRight: String): TRec;
class operator Equal (aLeft: TpColumn<T>; aRight: Integer): TRec;
end;
function FormCreate;
var vStrRec: TpColumn<String>;
vIntRec: TpColumn<Integer>;
begin
vCond1 := vStrRec = 'hello'; // should accept
vCond2 := vIntRec = 5; // should accept
vCond1 := vStrRec = 5; // should reject, currently accepted
end;
I could define TpColumnInt and TpColumnStr as distinct types, each its equality override (and it works), but I would like to define an array of TpColumn< T > regardless of the type T.
It isn't possible to use inheritance with records.
It isn't possible to define an array that contains different types (hence generics).
It isn't possible to override operators in classes (hence records).
The natural conclusion I've reached so far is Generic Records:
can put TpColumn< String > and TpColumn< Integer > in the same array.
EDIT: i understand that I was mistaken in this assumption, guess this approach won't work then.
can overload operators
The final piece of the puzzle that still needs to be fulfilled is compile-time checking in the Equal override. Where can I add such constraint that would solve this?
I was hoping something like this would do the trick, but it did not:
aLeft: TpColumn< String >; aRight: String
"operator Equal must take at least one TpColumn< T > type in parameters" - the compiler.
Is this achievable the way I'm trying? And if not, is there any other way to achieve it?

How to declare custom types in Delphi?

I want to declare a custom data type for better code readability. The intent is to keep this type clean from any interference with other AnsiStrings. But Delphi seems to not support it?
type
TKMLocaleCode = type AnsiString;
... snip ...
procedure A;
var
A,B: TKMLocaleCode;
C: AnsiString;
begin
A := 'eng'; //<<-- I expect an error here
A := C; //<<-- I expect an error here too
B := TKMLocaleCode('eng'); //<<-- I expect no error here
end;
Is it possible to declare such a custom type in Delphi?
You shouldn't have gotten an error where you did, but your initial technique wouldn't accomplish your goal anyway. Notice how TFileName is a distinct string type just like yours, but it can be used anywhere an ordinary string is expected. The type declaration is more for establishing different RTTI for a type so that it can be used for different kinds of property editors at design time.
To really make a distinct type, try declaring a record with a field to hold your data. Records are not compatible with anything else, even when they have the same structure as another type. To make your record comparable with other values of the same type, overload the comparison operator by providing Equal and NotEqual methods in the record declaration. To allow creation of the distinct type through type casting, but not through ordinary assignment, provide an Explicit operator, but not Implicit.
You can declare a record and then use operator overloading to supply whichever operators you wish to support:
type
TKMLocaleCode = record
strict private
FValue: AnsiString;
public
class operator Explicit(const Value: string): TKMLocaleCode;
end;
class operator TKMLocaleCode.Explicit(const Value: AnsiString): TKMLocaleCode;
begin
Result.FValue := Value;
end;
Clearly you'll want to add more functionality, but this record meets the requirements stated in the question.

What are the reasons to use TArray<T> instead of Array of T?

I'm migrating a legacy Delphi application to Delphi-XE2, and I'm wondering if there's a good reason to replace the arrays defined as Array of MyType to TArray<MyType>. So the question is what are the pros and cons of TArray<T> usage instead of Array of MyType?
The main advantage is less onerous type identity rules. Consider:
a: array of Integer;
b: array of Integer;
These two variables are not assignment compatible. It is a compiler error to write:
a := b;
On the other hand if you use the generic syntax:
a: TArray<Integer>;
b: TArray<Integer>;
then these two variables are assignment compatible.
Sure, you can write
type
TIntegerArray = array of Integer;
But all parties need to agree on the same type. It's fine if all code is in your control, but when using code from a variety of sources, the advent of generic dynamic arrays makes a huge difference.
The other advantage that springs to mind, in similar vein, is that you can readily use the generic array type as the return type of a generic method.
Without the generic array you are compelled to declare a type of this form:
TArrayOfT = array of T
in your generic class, which is rather messy. And if you are writing a generic method in a non-generic class, then you've no way to make that declaration. Again the generic array solves the problem.
TMyClass = class
class function Foo<T>: TArray<T>; static;
end;
This all follows on from type compatibility rules described in the documentation like this:
Type Compatibility
Two non-instantiated generics are considered assignment
compatible only if they are identical or are aliases to a
common type.
Two instantiated generics are considered assignment
compatible if the base types are identical (or are aliases to a
common type) and the type arguments are identical.
You can initialize TArray<T> with values with one construct:
var
LArray: TArray<Integer>;
begin
LArray := TArray<Integer>.Create(1, 2, 3, 4);
For array of Integer you would need to write much more code:
var
LArray: array of Integer;
begin
SetLength(LArray, 4);
LArray[0] := 1;
LArray[1] := 2;
LArray[2] := 3;
LArray[3] := 4;
It comes in handy for function results.
Example:
The following is not allowed in Delphi. You need to declare a separate type here. What a waste of time.
function MyFunc:array of integer;
begin
end;
Wait, generics to the resque:
function MyFunc:TArray<integer>;
begin
end;

why two aliases to "array of string" treated differently?

In Pascal there are two kinds of type declarations:
type aliases: type NewName = OldType
type creation: type NewType = type OldType
The former is just creating convenient shorthand, like typedef in C. The aliases are compatible one to another and to their original type. The created types are intentionally incompatible and cannot be mixed without explicit and unsafe by definition typecast.
var
nn: NewName; nt: NewType; ot: OldType;
...
nn := ot; // should work
nt := ot; // should break with type safety violation error.
nt := NewType(ot); // Disabling type safety. Should work even if
// it has no sense semantically and types really ARE incompatible.
Those are Pascal basics as i understand them.
Now let's look at one certain type and two its aliases:
System.Types.TStringDynArray = array of string;
System.TArray<T> = array of T;
in particular that means TArray<string> = array of string; by definition.
Now let's take function returning the former type alias and feed its result to the function expecting the latter one:
uses Classes, IOUtils;
TStringList.Create.AddStrings(
TDirectory.GetFiles('c:\', '*.dll') );
TStringList.Create.AddStrings(
TArray<string>( // this is required by compiler - but why ???
TDirectory.GetFiles('c:\', '*.dll') ) );
1st snippet would not compile due to types violation.
2nd one happily compiles and works, but is fragile towards future type changes and is redundant.
QC tells that compiler is right and the RTL design is wrong.
http://qc.embarcadero.com/wc/qcmain.aspx?d=106246
WHY compiler is right here ?
Why those aliases are incompatible ?
Even the very manner RTL was designed suggests that they were deemed compatible!
PS. David suggested even simplier example, without using TArray<T>
type T1 = array of string; T2 = array of string;
procedure TForm1.FormCreate(Sender: TObject);
function Generator: T1;
begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
procedure Consumer (const data: T2);
begin
with TStringList.Create do
try
AddStrings(data);
Self.Caption := CommaText;
finally
Free;
end;
end;
begin
Consumer(Generator);
end;
Same gotcha without explanation...
PPS. There are a number of doc refs now. I want to stress one thing: while this restriction might be indirectly inherited from Pascal Report of 1949, today is 2012 and Delphi used very differently from school labs of half-century ago.
I named few BAD effects of keeping this restrictions, and yet did not saw any good one.
Ironic thing, that this restricion may be lifted without breaking rules of Pascal: in Pascal there is no such non-strict beast as Open Arrays and Dynamic Arrays. So let those original fixed arrays be restricted as they wish, but Open Arrays and Dynamic Arrays are not Pascal citizens and are not obliged to be limited by its codebook!
Please, communicate Emba in QC or maybe even here, but if u just pass by without expressing your opinion - nothing would change!
The key to understanding this issue is the Type Compatibility and Identity topic in the language guide. I suggest you have a good read of that topic.
It is also helpful to simplify the example. The inclusion of generics in the example serves mainly to complicate and confuse matters.
program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}
type
TInteger1 = Integer;
TInteger2 = Integer;
TArray1 = array of Integer;
TArray2 = array of Integer;
TArray3 = TArray1;
var
Integer1: TInteger1;
Integer2: TInteger2;
Array1: TArray1;
Array2: TArray2;
Array3: TArray3;
begin
Integer1 := Integer2; // no error here
Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
Array1 := Array3; // no error here
end.
From the documentation:
When one type identifier is declared using another type identifier, without qualification, they denote the same type.
This means that TInteger1 and TInteger2 are the same type, and are indeed the same type as Integer.
A little further on in the documentation is this:
Language constructions that function as type names denote a different type each time they occur.
The declarations of TArray1 and TArray2 fall into this category. And that means that these two identifiers denote different types.
Now we need to look at the section discussing compatibility. This gives a set of rules to follow to determine whether or not two types are compatible or assignment compatible. We can in fact shortcut that discussion by referring to another help topic: Structured Types, Array Types and Assignments which states clearly:
Arrays are assignment-compatible only if they are of the same type.
This makes it clear why the assignment Array1 := Array2 results in a compiler error.
Your code looked at passing parameters, but mine focused on assignment. The issues are the same because, as the Calling Procedures and Functions help topic explains:
When calling a routine, remember that:
expressions used to pass typed const and value parameters must be assignment-compatible with the corresponding formal parameters.
.......
Delphi is a strongly typed language. That means that identical (in this case I mean their definitions look exactly the same) types are not assignment compatible.
When you write array of <type> you are defining a type and not an alias. As David already said in his comment the two identical types like
type
T1 = array of string;
T2 = array of string;
are not assignment compatible.
Same goes for
type
TStringDynArray = array of string;
TArray<T> = array of string;
Often people forget about the incompatibility of identical types and my guess would be that they did when they introduced IOUtils for example. Theoretically the definition of TStringDynArray should have been changed to TStringDynArray = TArray<string> but I guess that could have raised other problems (not saying bugs with generics...).
I also had the same problem with Delphi, where I wanted to pass values from one identical array to another. Not only did I have "incompatibility" problems with two like array assignments, but I also could not use the "Copy()" procedure. To get around this problem, I found that I could use a pointer to an type array of array of string, instead.
For example:
type RecArry = array of array of string
end;
var TArryPtr : ^RecArry;
Now, I can pass the values from any fixed array to another identical array without any compatibility or function problems. For example:
TArryPtr := #RecArry.LstArray //This works!
TArryPtr := #LstArray //This also works!
With this created array assignment template, I can now work with all two dimensional arrays without any problems. However, it should be understood, that when accessing this type of string array pointer, an extra element is created, so that when we would expect this type of array 2D array below, for example:
Two_Dimensional_Fixed_Array[10][0]
We now get an extra element adjusted array as seen here:
New_Two_Dimensional_Fixed_Array[10][1]
This means that we have to use some slightly tricky code to access the pointer array, because all the populated elements in Two_Dimensional_Fixed_Array[10][0] have moved down, so that they are offset by 1, as in New_Two_Dimensional_Fixed_Array[10][1].
Therefore where we would normally find the value 'X' in Two_Dimensional_Fixed_Array[1][0], it will now be found here in TArryPtr[0][1].
Its a trade off we all have to live with!
Another important note to bear in mind is the definition of a pointer array when it is declared. When a pointer array is type declared, the Borland compiler will not allow the Pointer array to have the same element size as the array to which it is pointing too. For example, if an array is declared as:
Orig_Arry : array [1..50,1] of string;
The pointer array which should point to it would be declared in the following fashion:
Type Pntr_Arry : array [1..50,2] of string;
Did you notice the the extra element? I am guessing the the Borland compiler has to widen the array pointer to allow for the pointer address.

Delphi Generic Types - Specificity?

I am trying to create a generic class under delphi called TRange. The idea is that it can be a Range of Integer or a range of single, or Double, etc...
The TRange object contains a few variables of type T (maxValue, minValue, idealValue, etc). The TRange contains a function for each of them to convert them into a string. However, since Delphi is a strong-typed language, I need to specify "How-To" convert the different variables into a string.
I can get the typeName of the T type using GetTypeName (TypeInfo (T)). Once I know which type is T, I thought I could do something like:
if(className = 'single') then
result := formatFloat('0.0', self.AbsMin as Single)
else
result := intToStr(self.AbsMin as Integer)
However, the compiler tells me "Operator not Applicable to this operand Type".
So, I guess my question is :
Is there a way to give specificity to a generic Class???
The compiler error comes from the fact that you cannot use the as operator to cast to a primitive type such as Single or Integer. Use a hard cast for that: Single(AbsMin).
Is there a way to give specificity to a generic Class???
Why do you need to convert the values to strings? This is kind of against the idea of a generic class, because you are now back to implementing special behaviour for all the cases.
If you really need this though you could introduce an interface
IValueStringConverter <T> = interface
function ToString(Value : T) : String;
end;
You can just supply the converter in the constructor of the TRange class and store it in a field:
constructor TRange <T>.Create(Converter : IValueStringConverter <T>);
begin
FConverter := Converter;
end;
Now just use the converter inside the class to do the conversion:
Str := FConverter.ToString(AbsMin);

Resources