I have a list of generics in which I want to put either some records or some classes
TMyList<T> = class
private
fCount: Cardinal;
fItems: array of T;
public
constructor Create(aSize: Integer);
procedure UpdateItem(const x: T);
end;
But I can't have the compiling
procedure TMyList<T>.UpdateItem(const x: T);
var
I: integer;
begin
for I := 0 to fCount - 1 do
if fItems[I] = x then begin // <- error E2015
//do update
break;
end;
end;
It works for classes with this declaration: TMyList<T : class> = class, but then it can't hold records anymore.
Of course, for the record I declare class operator Equal(Left, Right : TMyRecord) : Boolean; so that MyRecord1 = MyRecord2 would compile.
This can never be made to work using the = operator. The reason is that generic constraints are not rich enough to specify the availability of operators. You simply cannot use the = operator on generic operands.
You can do so if you constrain the operands to be a class, because a classes are references, and the compiler knows how to compare references for equality. Basically the compiler needs to know how to generate the code when it compiles the generic class. Unlike C++ or Smalltalk templates, with generics the compiler does not wait until instantiation to compile the code.
If you want to use a custom comparer then you will need to supply that explicity. Which is rather frustrating, I know. If you can make do with the default comparer you can use:
TEqualityComparer<T>.Default
Related
I want to do this in XE5:
type
TMyRec = record
// fields
class function GetList: TMyRecArr; static;
end;
TMyRecArr = array of TMyRec;
I've already seen "Forward declarations for record types" and "how to do a typed forward declaration?", but they seem irrelevant since my problem is not passing the record as a parameter.
You cannot use a forward declaration to declare a record type or an array type. But not to fear. You can use a generic dynamic array, TArray<T>.
type
TMyRec = record
class function GetList: TArray<TMyRec>; static;
end;
This is actually better than declaring TMyRecArr as per the code in your question. That's because the generic TArray<T> has more flexible type identity than a traditional dynamic array type. You can use TArray<T> with generic types defined in libraries that are independent and unaware of your code.
Now, you could declare the type like this:
type
TMyRec = record
type TMyRecArray = array of TMyRec;
class function GetList: TMyRecArray; static;
end;
And then your array type is TMyRec.TMyRecArray. But I urge you not to do this. You'll have a type that can only be used with you code, and cannot be used with third party code.
In summary, TArray<T> is your friend.
Since declarations of pointers types are permitted before the type definition, slightly modifying your function you're allowed to do this:
type
PMyRecArr = ^TMyRecArr;
TMyRec = record
// fields
class procedure GetList(const arr: PMyRecArr); static;
end;
TMyRecArr = array of TMyRec;
The procedure implementation and its usage follow:
class procedure TMyRec.GetList(const arr: PMyRecArr);
begin
SetLength(arr^, 4);
end;
var
arr: TMyRecArr;
begin
TMyRec.GetList(#arr);
Writeln(Length(arr));//prints 4
end.
Well, I searched a little more, and found that I can use helpers to do this:
type
TMyRec = record
// fields
end;
TMyRecArr = array of TMyRec;
TMyRecHelper = record helper for TMyRec
class function GetList: TMyRecArr; static;
end;
Sure it lacks benefits of generics that David mentioned in his answer and comments, but it doesn't make the code auto-complete unusable! I mean one may comes to this conclusion that the flexibility that TArray<T> offers is not needed in some piece of code. Then it would be nothing more than increment in memory usage of the final running application or possibly lower performance.
Instead of trying to add forward decalrations to records (which up until now still isn't possible in Delphi), there is a way around this : move functions that reference another record type towards a record helper, declared where both record types are in scope.
type
RecordA = record
// [...]
end;
RecordB = record
// [...]
end;
RecordAHelper = record helper for RecordA
procedure Call(argument: RecordB);
end;
After obvioulsy implementing this, above allows one to write (and compile and run) : RecordA_variable.Call(RecordB_variable)
I'm using a record to encapsulate two dissimular sets.
I've put in operators to allow the assignment of either set to the record. Doing so will clear the other set.
However I cannot assign an empty set.
See the following example code:
Program test;
{$Apptype console}
type
TSomeThing = (a,b,c);
TOtherThing = (x,y,z);
TSomeThings = set of TSomething;
TOtherThings = set of TOtherThing;
TSomeRecord = record
strict private
Fa: TSomeThings;
Fb: TOtherThings;
public
class operator Implicit(a: TSomeThings): TSomeRecord;
class operator Implicit(a: TOtherThings): TSomeRecord;
end;
implementation
class operator TSomeRecord.Implicit(a: TSomeThings): TSomeRecord;
begin
Result.Fa:= a;
Result.Fb:= [];
end;
class operator TSomeRecord.Implicit(a: TOtherThings): TSomeRecord;
begin
Result.Fa:= [];
Result.Fb:= a;
end;
var
SomeRec: TSomeRecord;
begin
SomeRec:= [];
end.
[dcc64 Error] InstructionList.pas(512): E2010 Incompatible types: 'TSomeRecord' and 'Set'
How do I make it so I can assign the empty set to my record?
I can misuse the implicit operator to allow SomeRec:= nil;, but that looks very ugly.
The compiler cannot tell whether you mean an empty set of TSomeThing or an empty set of TOtherThing. You can declare typed constants to allow the compiler to resolve the overload:
const
EmptySomeThings: TSomeThings = [];
EmptyOtherThings: TOtherThings = [];
Then the following assignments compile and resolve as you would expect:
SomeRec:= EmptySomeThings;
SomeRec:= EmptyOtherThings;
Of course, you know that either one of these has the same effect, because the implementation of the Implicit operators sets one field, and clears the other. But the compiler cannot know this.
If you wish to clear both members of the record you can always use:
SomeRec:= Default(TSomeRecord);
I personally might wrap that up in a static class method like this:
class function Default: TSomeRecord; static;
....
class function TSomeRecord.Default: TSomeRecord;
begin
Result := Default(TSomeRecord);
end;
Then you can write:
SomeRec:= TSomeRecord.Default;
In an ideal world you'd be able to declare a constant in the type, but the language designers did not think of that and it is sadly not possible.
Update
Rudy correctly points out in a comment, that constants can be added to a record type by way of a record helper. This was news to me as I mistakenly believed that helpers could only add methods. This is what I love about Stack Overflow. Even when you think you know something pretty well, there's always scope for more knowledge to be acquired. Thanks Rudy.
So you might write:
type
TSomeRecordHelper = record helper for TSomeRecord
public
const
Default: TSomeRecord = ();
end;
In Delphi XE2, I want to write a generic collection class which manipulates objects which must have a Copy(owntype) method, but I can't figure out how best to declare this.
I want something like this example (a collection of one item, for simplicity):
//------ Library ------
Type
TBaseCopyable = class
S: string;
// procedure Copy(OtherObject: TBaseCopyable); overload;
procedure Copy(OtherObject: TBaseCopyable); virtual;
end;
MyCollection<T: TBaseCopyable, constructor> = class
TheItem: T;
procedure SetItem(AItem: T);
function GetItem: T;
end;
[...]
function MyCollection<T>.GetItem: T;
Var
NewItem: T;
begin
NewItem := T.Create;
NewItem.Copy(TheItem);
Result := NewItem;
end;
//------ Usage ------
Type
TMyCopyable = class(TBaseCopyable)
I: integer;
// procedure Copy(OtherObject: TMyCopyable); overload;
procedure Copy(OtherObject: TMyCopyable); override;
end;
[...]
Col: MyCollection<TMyCopyable>;
The key problem is that in Col, I need the generic implementation of MyCollection to find TMyCopyable.Copy. Unsurprisingly, neither overload or virtual do the job:
With overload, the code compiles, but MyCollection.GetItem finds
TBaseCopyable.Copy, not TMyCopyable.Copy.
With virtual/override this
doesn't compile because the signatures of the two Copy declarations
don't match.
So I figure I need to use generics somehow in the specification of TBaseCopyable, possibly instead of inheritance. But I'm not sure how, primarily because I don't particularly need to feed a type parameter into TBaseCopyable, I just need the Copy argument type to refer to "the type of it's own class" in a generic way.
Ideas? Thanks!
Turn TBaseCopyable into a Generic class and apply its Generic type to Copy(), then TMyCopyable can override it, eg:
type
TBaseCopyable<T> = class
S: string;
procedure Copy(OtherObject: T); virtual;
end;
MyCollection<T: TBaseCopyable<T>, constructor> = class
TheItem: T;
procedure SetItem(AItem: T);
function GetItem: T;
end;
type
TMyCopyable = class(TBaseCopyable<TMyCopyable>)
I: integer;
procedure Copy(OtherObject: TMyCopyable); override;
end;
Alternatively, just do the same thing that TPersistent.Assign() does (since it does not use Generics):
type
TBaseCopyable = class
S: string;
procedure Copy(OtherObject: TBaseCopyable); virtual;
end;
MyCollection<T: TBaseCopyable, constructor> = class
TheItem: T;
procedure SetItem(AItem: T);
function GetItem: T;
end;
type
TMyCopyable = class(TBaseCopyable)
I: integer;
procedure Copy(OtherObject: TBaseCopyable); override;
end;
procedure TMyCopyable.Copy(OtherObject: TBaseCopyable);
begin
inherited;
if OtherObject is TMyCopyable then
I := TMyCopyable(OtherObject).I;
end;
Answering my own question, or at least summarizing findings:
So far as I can tell, there is no complete answer to the question as I posed it. What I have learned is this:
[1] Remy's solution is the way to go if the base item class (here TBaseCopyable) has no state, and either is abstract, or methods don't need to refer to other objects of the same type. (Eg: TBaseCopyable would have no fields and only abstract methods.)
[2] A significant issue is how to specify a generic class whose descendant classes can specify method arguments and return values of the same type as their enclosing class. In Remy's example, that is accomplished in the descendant class declaration:
TMyCopyable = class(TBaseCopyable<TMyCopyable>)
This means that in the generic class, T will be replaced by the ultimate class of interest.
[3] However, within TBaseCopyable's generic declaration, the information that T is always a TBaseCopyable is not available, so in TBaseCopyable's implementation, references to objects of type T won't be able to see TBaseCopyable's methods or fields.
This would be solved if we could set a constraint on T to tell the compiler that T is a TBaseCopyable.
That's apparently the approach in C#:
http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx
In Delphi, I think that would go like this:
type
TBaseCopyable<T: TBaseCopyable<T> > = class
...
like Remy shows for MyCollection. However, that syntax is not legal within the same class declaration (error: undeclared identifier TBaseCopyable), because TBaseCopyable is not yet fully defined. We might think to create a forward declaration for TBaseCopyable (like we would for non-generic classes), but that throws an error, and apparently it's not supported by the compiler:
How to set a forward declaration with generic types under Delphi 2010?
E2086 error with forward declaration of a generic type http://qc.embarcadero.com/wc/qcmain.aspx?d=94044
[4] Maybe the generic class could inherit the implementation?
What if we did this:
type
TBaseCopyable<T> = class(TBaseCopyableImpl) ...
That would allow TBaseCopyable to have some fields and methods that could refer to each other. However, even if those methods were virtual, they would impose fixed argument/return types on the descendants, the avoidance of which was the rationale for using generics in the first place.
So this strategy is only good for fields and methods that don't need to specialize in the descendant types... for example an object counter.
Conclusions
This question turns out to concern the somewhat-known "Curiously recurring template pattern": http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern. Even though it seems what one is trying to accomplish is simple, there are theoretical problems behind the scenes.
The situation appears to call for a language keyword meaning something like "Same type as my enclosing class". However that apparently leads to covariance/contravariance issues -- violations of the rules of which types can substitute for which in inheritance hierachies. That said, it seems Delphi doesn't go as far as C# to permit as much of a partial solution.
Of course, I'd be happy to learn that there is a way to go further!
Oh, and I don't feel too bad struggling to get to the bottom of this -- even Ken Arnold thinks it's difficult: https://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid.html#comment-828994
:-)
Take this small example class (not my real code, but it exposes the problem):
Convert = class(TObject)
public
class function ToString(value: Double): String; overload;
class function ToString(value: TDateTime): String; overload;
end;
It compiles fine until you try to use the Double or TDateTime functions As
In:
var
d: Double;
begin
d := 99.99;
ShowMessage(Convert.ToString(d));
You will get this compile error: Ambiguous overloaded call to 'ToString'.
The problem boils down to the fact that TDateTime is a type of Double
My Question: how do You deal with this type of problem?
EDIT - I am NOT looking for a solution for the example given
I have found 3 Solutions so far:
Rename one of the 2 functions
Add a "Dummy" parameter to one of the 2 functions
Change the parameters to Var types, this has the disadvantage that I can no longer call this function with constants
are there any other solutions out there?
Overloaded methods can be very effective. However, as soon as there is a hint of ambiguity they become a liability. A good example of this are the new TStream overloads introduced in XE3. It's not hard to fall into a trap where the compiler chooses an overload that you weren't expecting. At least in your code the compiler stopped. In that sense you were lucky.
So my advice, in your situation, is to abandon overloads. Express the different input types in the method name. Yes it's a little more verbose, but you won't make any mistakes, and you code will compile!
Your posted example compiles and executes fine in XE.
In a comment you give this example instead:
ShowMessage( Convert.ToString( 99.99 )); // <- gives compiler error 2251
In this particular case the solution is to explicitly define the type( I thought):
ShowMessage( Convert.ToString( Double(99.99) )); // <- E2089, Invalid Typecast
Looking into the documentation:
This error message is issued for type casts not allowed by the rules. The following kinds of casts are allowed:
Ordinal or pointer type to another ordinal or pointer type
A character, string, array of character or pchar to a string
An ordinal, real, string or variant to a variant
A variant to an ordinal, real, string or variant
A variable reference to any type of the same size.
So, to explicitly tell the compiler to select the Double overloaded function:
ShowMessage( Convert.ToString( Double(Variant(99.99)))); // Ok
A bit convoluted perhaps. But for the other overloaded function it is simpler:
ShowMessage( Convert.ToString( EncodeDate(2013,1,5));
Update
To make this a generic solution working for all classes, consider adding class functions to resolve your ambiguous types.
Convert = Class(TObject)
...
class function AsDouble( value: Double) : Double; inline; static;
class function AsTDateTime( value: TDateTime) : TDateTime; inline; static;
end;
class function Convert.AsDouble(value: Double): Double;
begin
Result := Value;
end;
class function Convert.AsDateTime(value: TDateTime): TDateTime;
begin
Result := Value;
end;
Now you can call your overloaded class function with constants:
ShowMessage( Convert.ToString( Convert.AsDouble(99.99)));
How about collapsing it all?:
class function Convert.ToString(value: Variant): String;
begin
Result := VarToStr(Value);
end;
I have read how to make a pointer to a normal class and use it inside the class difinition:
type
PExample = ^TExample;
TExample = class
data: Integer;
next: PExample;
end;
but how do you do it with templatized parameters? This does not compile with the error Undeclared identifier: 'TExample' on the second line:
type
PExample = ^TExample;
TExample<T> = class
data: T;
next: PExample;
end;
Changing it to
PExample = ^TExample<T>;
does not fix it.
As you are using a class, you don't need to use a PExample. Classes are already reference types.
TExample<T> = class
data: T;
next: TExample<T>;
end;
This should work without the need to declare any pointer types. Pointer types are only required if you work with records (which are value types).
EDIT:
To my surprise I just noticed that this compiles and works in Delphi XE:
program Project;
{$APPTYPE CONSOLE}
type
TNode<T> = record
Next: ^TNode<T>;
Data: T;
end;
var
Node1, Node2: TNode<Integer>;
begin
Node1.Next := #Node2;
Node1.Data := 1;
Node2.Next := nil;
Node2.Data := 2;
WriteLn(Node1.Data);
WriteLn(Node1.Next.Data);
end.
It does still not solve the problem of defining a general generic pointer type, because this:
PNode<T> = ^TNode<T>;
does not work.
The other answers tell you how to build a generic linked list of classes. If you ever need to build a generic linked list of records, you cannot do so at present:
type
PNode<T> = ^TNode<T>;
TNode<T> = record
public
Next: PNode;
end;
does not compile.
Nor does:
type
TNode<T> = record
type
PNode = ^TNode;
public
Next: PNode;
end;
I believe that the reason for this is that the single-pass compiler does not support forward declarations of methods. This is actually more of a practical problem for operator overloading than for generics because classes can be used for generic linked lists.
Is it necessary to use a pointer to a class? Using a class reference for your "next" field should work, like this:
type
TExample<T> = class
data: T;
next: TExample<T>;
end;
The "next" field can still be NIL or can be assigned another TExample instance. This seems to negate the need of using a traditional ^ pointer (although you could argue that a reference to a class is also a pointer, just with different syntax).