I'm struggling with Spring4D (1.2.2) TMultiMap generic class. I want to call an overloaded constructor and the compiler complain:
"E2250 There is no overloaded version of 'Create' that can be called with these arguments"
The argument is the correct type according to the Spring4D source code.
I devised a small do-nothing program reproducing the error:
program Spring4DMultiMapTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Generics.Collections,
Spring.Tests.Interception.Types,
Spring.Collections.MultiMaps,
Spring.Collections;
type
TMyKey = TPair<Double, Integer>;
TMyKeyEqualityComparer = class(TInterfacedObject, IEqualityComparer<TMyKey>)
function Equals(const Left, Right: TMyKey): Boolean; reintroduce;
function GetHashCode(const Value: TMyKey): Integer; reintroduce;
end;
function TMyKeyEqualityComparer.Equals(const Left, Right: TMyKey): Boolean;
begin
if Left.Key < (Right.Key - 1E-9) then
Result := TRUE
else if Left.Key > (Right.Key + 1E-9) then
Result := FALSE
else if Left.Value < Right.Value then
Result := TRUE
else
Result := FALSE;
end;
function TMyKeyEqualityComparer.GetHashCode(const Value: TMyKey): Integer;
begin
Result := Value.Value + Round(Value.Key * 1E6);
end;
var
Events : IMultiMap<TMyKey, Integer>;
KeyComparer : IEqualityComparer<TMyKey>;
begin
KeyComparer := TMyKeyEqualityComparer.Create;
// Next line triggers error: "E2250 There is no overloaded version of 'Create' that can be called with these arguments"
Events := TMultiMap<TMyKey, Integer>.Create(KeyComparer);
end.
In Spring4D source code, I find the following declaration:
TMultiMap<TKey, TValue> = class(TMultiMapBase<TKey, TValue>)
and also the TMultiMap acestor is declared:
TMultiMapBase<TKey, TValue> = class abstract(TMapBase<TKey, TValue>,
IMultiMap<TKey, TValue>, IReadOnlyMultiMap<TKey, TValue>)
and has this constructor:
constructor Create(const keyComparer: IEqualityComparer<TKey>); overload;
This is the constructor I want to call. As far as I understand, my argument KeyComparer has the correct type. But obviously the compiler doesn't agree :-(
How to fix this code?
You use different IEqualityComparer<T> types.
The IEqualityComparer<T> in your example comes from Spring.Tests.Interception.Types.pas. The one used in TMultiMap<TKey, TValue>.Create constructor is defined in System.Generics.Defaults.pas.
If you change your uses part to
uses
System.SysUtils,
Generics.Collections,
// Spring.Tests.Interception.Types,
Generics.Defaults,
Spring.Collections.MultiMaps,
Spring.Collections;
your example will compile.
Tested with Delphi 10.3 U3 and Spring4D 1.2.2
Related
I'm using Delphi 10.3 Community Edition, targeting Windows 32-bit.
I'm writing an interface type. Its class internally uses a TList<T> (and some other objects). In order to avoid having to write several pass-through one-liner methods, I'm using the implements keyword to delegate, like this:
program MRE;
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
IMyList = interface
['{8483DB08-57EA-40D0-BFCE-5E39804CA15B}']
function Add (const ACard: Cardinal): Integer;
function GetEnumerator: TList<Cardinal>.TEnumerator;
end;
TMyList = class (TInterfacedObject, IMyList)
constructor Create;
destructor Destroy; override;
private
FCards: TList<Cardinal>;
public
property Cards: TList<Cardinal>
read FCards
implements IMyList;
end;
constructor TMyList.Create;
begin
FCards := TList<Cardinal>.Create;
end;
destructor TMyList.Destroy;
begin
FCards.Free
end;
begin
var Test: IMyList := TMyList.Create;
Test.Add (3);
for var c in Test do
WriteLn (c);
ReadLn
end.
This works fine and prints 3, as expected.
However, when I experienced strange crashes in the more complex version of the program, I was able to distill the bug down to the declaration of a type. Just add a single line (anywhere between the type declarations):
type
TCardList = TList<Cardinal>; { **** this isn't even used anywhere! }
The code still compiles, but the application now crashes with an Access Violation ('write of address 0x004ee838'). It's not just the call to Add(). When you remove that, it still crashes when the for loop tries to get the enumerator.
Are there some weird restrictions to delegation that I'm missing here, or could this be a compiler deficiency?
EDIT: As requested in the comments, here is the same code again with the single line that triggers the crash already added:
program MRE;
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
TCardList = TList<Cardinal>; { **** this isn't even used anywhere! }
IMyList = interface
['{8483DB08-57EA-40D0-BFCE-5E39804CA15B}']
function Add (const ACard: Cardinal): Integer;
function GetEnumerator: TList<Cardinal>.TEnumerator;
end;
TMyList = class (TInterfacedObject, IMyList)
constructor Create;
destructor Destroy; override;
private
FCards: TList<Cardinal>;
public
property Cards: TList<Cardinal>
read FCards
implements IMyList;
end;
constructor TMyList.Create;
begin
FCards := TList<Cardinal>.Create;
end;
destructor TMyList.Destroy;
begin
FCards.Free
end;
begin
var Test: IMyList := TMyList.Create;
Test.Add (3);
for var c in Test do
WriteLn (c);
ReadLn
end.
This produces the crash mentioned above.
Long story short: The following piece of code does not compile in Delphi 10.1 Berlin (Update 2).
interface
uses
System.Classes, System.SysUtils;
type
TTest = class(TObject)
public
function BuildComponent<T: TComponent>(const AComponentString: String): T;
end;
TSomeComponent = class(TComponent)
public
constructor Create(AOwner: TComponent; const AString: String); reintroduce;
end;
implementation
{ TTest }
function TTest.BuildComponent<T>(const AComponentString: String): T;
begin
if T = TSomeComponent then
Result := TSomeComponent.Create(nil, AComponentString)
else
Result := T.Create(nil);
end;
{ TSomeComponent }
constructor TSomeComponent.Create(AOwner: TComponent; const AString: String);
begin
inherited Create(AOwner);
end;
Several error messages are emitted from the compiler:
E2015: Operator not applicable to this operand type
on line if T = TSomeComponent then and
E2010 Incompatible types - 'T' and 'TSomeComponent'
on line Result := TSomeComponent.Create(nil, AComponentString).
To circumvent these, I could cast TClass(T) (for #1), as described in LU RD's answer here (despite it is said, that this bug has already been fixed in XE6), and T(TSomeComponent.Create(nil, AComponentString))(for #2). Although, I feel uncomfortable using explicit type-casting.
Is there any better way? Shouldn't the compiler recognize, that T is of type TComponent because I constrained it explicitly?
At first, I tried to declare the generic function's implementation like it's interface:
function TTest.BuildComponent<T: TComponent>(const AComponentString: String): T;
But this ended up with the error
E2029: ',', ';' or '>' expected but ':' found
This does not compile in any version of Delphi that I have encountered. You need to do some casting to persuade the compiler to compile this:
function TTest.BuildComponent<T>(const AComponentString: String): T;
begin
if TClass(T) = TSomeComponent then
Result := T(TSomeComponent.Create(nil, AComponentString))
else
Result := T(TComponentClass(T).Create(nil));
end;
That said, I think that I might prefer:
if TClass(T).InheritsFrom(TSomeComponent) then
in place of that equality test.
Even then, trying to splice in a new constructor with different arguments, to a class based on a virtual constructor looks like a recipe for disaster to me.
I am trying to create a class that implements an interface but I get these errors:
[dcc32 Error] dl_tPA_MailJournal.pas(10): E2291 Missing implementation of interface method IInterface.QueryInterface
[dcc32 Error] dl_tPA_MailJournal.pas(10): E2291 Missing implementation of interface method IInterface._AddRef
[dcc32 Error] dl_tPA_MailJournal.pas(10): E2291 Missing implementation of interface method IInterface._Release
[dcc32 Fatal Error] MainUnit.pas(8): F2063 Could not compile used unit 'dl_tPA_MailJournal.pas'
The code is:
unit dl_tPA_MailJournal;
interface
uses
Windows,
Generics.Collections,
SysUtils,
uInterfaces;
type
TtPA_MailJournal = class(TObject, ITable)
public
function GetanQId: integer;
procedure SetanQId(const Value: integer);
function GetadDate: TDateTime;
procedure SetadDate(const Value: TDateTime);
function toList: TList<string>;
constructor Create(aId : Integer; aDate : TDateTime);
private
property anQId : integer read GetanQId write SetanQId;
property adDate : TDateTime read GetadDate write SetadDate;
end;
implementation
{ TtPA_MailJournal }
constructor TtPA_MailJournal.Create(aId : Integer; aDate : TDateTime);
begin
SetanQId(aId);
SetadDate(aDate);
end;
function TtPA_MailJournal.GetadDate: TDateTime;
begin
Result := adDate;
end;
function TtPA_MailJournal.GetanQId: integer;
begin
Result := anQId ;
end;
procedure TtPA_MailJournal.SetadDate(const Value: TDateTime);
begin
adDate := Value;
end;
procedure TtPA_MailJournal.SetanQId(const Value: integer);
begin
anQId := Value;
end;
function TtPA_MailJournal.toList: TList<string>;
var
aListTable: TList<TtPA_MailJournal>;
aTable: TtPA_MailJournal;
aListString: TList<String>;
begin
aTable.Create(1,now);
aListTable.Add(aTable);
aTable.Create(2,now);
aListTable.Add(aTable);
aListString.Add(aListTable.ToString);
Result := aListString;
end;
end.
And the interface is:
unit uInterfaces;
interface
uses
Generics.Collections;
type
ITable = Interface
['{6CED8DCE-9CC7-491F-8D93-996BE8E4D388}']
function toList: TList<string>;
end;
implementation
end.
The problem is that you use TObject as the parent for your class. You should use TInterfacedObject instead.
In Delphi, every interface inherits from IInterface at therefore has, at least, the following 3 methods:
_AddRef
_Release
QueryInterface
You must implement these 3 methods, either by implementing them yourself or by inheriting from a base object that includes these methods.
Because you inherit from TObject, but you are not implementing these 3 methods, you get a compilation error. If you read the compiler error, you will see that it actually spells out this omission for you.
TInterfacedObject has already implemented these methods for you.
Other base objects that implement IInterface (aka IUnknown) are: TAggregatedObject and TContainedObject. However these are special purpose vehicles, only to be used if you really know what you're doing.
Change the definition of your class to
TTPA_MailJournal = class(TInterfacedObject, ITable)
And your code will compile.
See Delphi basics for more info.
I'm getting a IEnumVariant from a .NET class library and I am trying to use a generic class to convert this to a IEnumerator
There is a compiler error, "Operator not applicable to this operand type" when attempting to cast an IInterface to the generic type T
I've seen workarounds when attempting to type cast to a class, but these don't work for an interface.
Using Supports as suggested by Rob seems to have problems as well as TypeInfo returns nil for the parameterized type.
uses WinApi.ActiveX, Generics.Collections;
type
TDotNetEnum<T: IInterface> = class(TInterfacedObject, IEnumerator<T>)
strict private
FDotNetEnum: IEnumVariant;
FCurrent: T;
function MoveNext: Boolean;
procedure Reset;
function GetCurrent: TObject;
function IEnumerator<T>.GetCurrent = GenericGetCurrent;
function GenericGetCurrent: T;
public
constructor Create(const ADotNetObject: OleVariant);
//// I can get it to work using this constructor
// constructor Create(const ADotNetObject: OleVariant; const AGUID: TGUID);
end;
implementation
uses System.Rtti, SysUtils, mscorlib_TLB, ComObj;
constructor TDotNetEnum<T>.Create(const ADotNetObject: OleVariant);
var
netEnum: IEnumerable;
begin
netEnum := IUnknown(ADotNetObject) as mscorlib_TLB.IEnumerable;
FDotNetEnum := netEnum.GetEnumerator();
end;
function TDotNetEnum<T>.GenericGetCurrent: T;
begin
result := FCurrent;
end;
function TDotNetEnum<T>.GetCurrent: TObject;
begin
result := nil;
end;
function TDotNetEnum<T>.MoveNext: Boolean;
var
rgvar: OleVariant;
fetched: Cardinal;
ti: TypeInfo;
guid: TGUID;
begin
OleCheck(FDotNetEnum.Next(1, rgvar, fetched));
result := fetched = 1;
if not result then
FCurrent := nil
else
begin
FCurrent := IUnknown(rgvar) as T; // <-- Compiler error here
//// Doesn't work using Supports either
// ti := TypeInfo(T); // <-- returns nil
// guid := GetTypeData(#ti)^.Guid;
// Supports(IUnknown(rgvar), guid, FCurrent);
end;
end;
procedure TDotNetEnum<T>.Reset;
begin
OleCheck(FDotNetEnum.Reset);
end;
Am I missing something in order to get that case to the generic interface type to work ?
I do have the alternative constructor which I CAN get the guid from so that
TDotNetEnum<IContact>.Create(vContactList, IContact);
works but the ideal
TDotNetEnum<IContact>.Create(vContactList);
doesn't
Using as to cast interfaces is only valid for interfaces that have GUIDs. The compiler cannot assume that T has a GUID when it's compiling your generic class, so it cannot accept an expression of the form val as T.
This has been covered before, but in reference to the Supports function, which has the same limitation as the as operator.
The solution is to use RTTI to fetch the interface's GUID, and then use that to type-cast the interface value. You could use Supports:
guid := GetTypeData(TypeInfo(T))^.Guid;
success := Supports(IUnknown(rgvar), guid, FCurrent);
Assert(success);
You could also call QueryInterface directly:
guid := GetTypeData(TypeInfo(T))^.Guid;
OleCheck(IUnknown(rgvar).QueryInterface(guid, FCurrent));
I would like to declare a generic record like so:
type
TMyDelegate<T: constraint> = record
private
fDelegate: T;
public
class operator Implicit(a: T): TMyDelegate;
class operator Implicit(A: TMyDelegate: T);
end;
I'd like to limit T to reference to procedure/function. (As much as possible).
I've tried this, but it does not compile:
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TProc1 = reference to procedure(a: Integer);
TProc2 = reference to procedure(b: TObject);
TTest<T: TProc1, TProc2> = record
private
fData: T;
public
class operator Implicit(a: T): TTest<T>;
class operator Implicit(a: TTest<T>): T;
end;
{ TTest<T> }
class operator TTest<T>.Implicit(a: T): TTest<T>;
begin
Result.fData:= a;
end;
class operator TTest<T>.Implicit(a: TTest<T>): T;
begin
Result:= a.fData;
end;
var
Delegate1: TProc1;
Delegate2: TProc2;
var
MyTest1: TTest<TProc1>; <<-- error
MyTest2: TTest<TProc2>;
begin
MyTest1:=
procedure(a: Integer)
begin
WriteLn(IntToStr(a));
end;
end.
This gives compile error:
[dcc32 Error] Project3.dpr(39): E2514 Type parameter 'T' must support interface 'TProc2'
Is there a way to constrain a generic type to (a list of) anonymous types?
David's is the correct answer but as a work around something like this may help:
program Project51;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TTest<T> = record
type
TProcT = reference to procedure(a: T);
private
fData: TProcT;
public
class operator Implicit(a: TProcT): TTest<T>;
class operator Implicit(a: TTest<T>): TProcT;
end;
{ TTest<T> }
class operator TTest<T>.Implicit(a: TProcT): TTest<T>;
begin
Result.fData:= a;
end;
class operator TTest<T>.Implicit(a: TTest<T>): TProcT;
begin
Result:= a.fData;
end;
var
MyTest1: TTest<Integer>;
MyTest2: TTest<TObject>;
begin
MyTest1:=
procedure(a: Integer)
begin
WriteLn(IntToStr(a));
end;
MyTest2:=
procedure(a: TObject)
begin
WriteLn(a.ClassName);
end;
end.
There is no way to specify such a constraint. Possible constraints are:
Value type.
Class, derived from specific ancestor.
Interface, derived from specific ancestor.
Parameterless constructor.
This is covered in the documentation: http://docwiki.embarcadero.com/RADStudio/en/Constraints_in_Generics
What the documentation does not make clear is that reference procedures types count as interfaces. Which is why your generic type compiles, with that constraint. But this is never any use to you. Because reference procedure types have no inheritance. And so the only thing that can meet a specific reference procedure type constraint is something of that specific type.
In fact your type cannot be instantiated. That's because the constraint
T: TProc1, TProc2
specifies that T supports both of those reference procedure interfaces. And nothing can do that. Nothing can simultaneously support both TProc1 and TProc2.
Thanks to GrayMatter and David I've come up with a solution to the problem.
The solution is to redefine the anonymous procedure to fit within the constraints.
The following functions are defined.
TA = reference to procedure(const &In, &Out: TArray<TOmniValue>);
TB = reference to procedure(const &In, &Out: TArray<IOmniBlockingCollection>);
TC = .....
The trick is to redefine the methods like so:
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
IData<Tin, Tout> = interface
['{D2132F82-CAA9-4F90-83A9-9EFD6221ABE2}']
function GetInput: TArray<TIn>;
function GetOutput: TArray<Tout>;
end;
TData<TIn, TOut> = class(TInterfacedObject, IData<Tin, Tout>)
private
fInput: TArray<Tin>;
fOutput: TArray<Tout>;
public
constructor Create(const input: TArray<TIn>; const output: TArray<TOut>);
function GetInput: TArray<Tin>;
function GetOutput: TArray<Tout>;
end;
TDelegate<Tin, Tout> = reference to procedure(const Data: IData<TIn, TOut>);
{ TSimpleData }
constructor TData<TIn, TOut>.Create(const input: TArray<TIn>;
const output: TArray<TOut>);
begin
finput:= input;
foutput:= output;
end;
function TData<Tin, Tout>.GetInput: TArray<Tin>;
begin
Result:= fInput;
end;
function TData<Tin, Tout>.GetOutput: TArray<TOut>;
begin
Result:= fOutput;
end;
var
IntegerDelegate: TDelegate<Integer, Integer>;
input, output: TArray<Integer>;
i: Integer;
Data: TData<Integer, Integer>;
begin
IntegerDelegate:= procedure(const Data: IData<Integer, Integer>)
var
i: Integer;
input: TArray<Integer>;
begin
input:= Data.GetInput;
for i:= 0 to High(input) do begin
Data.GetOutput[i]:= input[i]+10;
end;
end;
SetLength(input,10);
SetLength(output, Length(input));
for i:= Low(input) to High(input) do begin
input[i]:= i;
end;
Data:= TData<Integer, Integer>.Create(input, output);
IntegerDelegate(Data);
for i in output do Writeln(i);
Readln;
end.
I can now limit the delegate to the types allowed (more or less).