Dealing with overloaded functions that have ambiguous parameters - delphi

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;

Related

Delphi - How can I pass Generic parameter to function that accept Array of const parameter

I have a 'Base class' which contain a 'function' that accept parameter of type 'Array of const' as shown below:-
type
TBaseClass = class(TObject)
public
procedure NotifyAll(const AParams: array of const);
end;
procedure TBaseClass.NotifyAll(const AParams: array of const);
begin
// do something
end;
I have another 'Generic class' that is derived from the 'base class' ( defined above )
type
TEventMulticaster<T> = class(TBaseClass)
public
procedure Notify(AUser: T); reintroduce;
end;
procedure TEventMulticaster<T>.Notify(AUser: T);
begin
inherited NotifyAll([AUser]); ERROR HERE
end;
Every time I compile this code it gives error saying:
Bad argument type in variable type array constructor
What does it referring to be wrong?
You cannot pass a Generic argument as a variant open array parameter. The language Generics support simply does not cater for that.
What you can do instead is wrap the Generic argument in a variant type, for instance TValue. Now, you cannot pass TValue instances in a variant open array parameter either, but you can change NotifyAll() to accept an open array of TValue instead:
procedure NotifyAll(const AParams: array of TValue);
Once you have this in place, you can call it from your Generic method like so:
NotifyAll([TValue.From<T>(AUser)]);
Fundamentally, what you are attempting to do here is combine compile-time parameter variance (Generics) with run-time parameter variance. For the latter, there are various options. Variant open array parameters are one such option, but they do not play well with Generics. The alternative that I suggest here, TValue, does have good interop with Generics.
The System.Rtti unit has something exactly for you needs, but not widely known:
TValueArrayToArrayOfConst() and
ArrayOfConstToTValueArray()
So your implementation should be:
procedure TEventMulticaster<T>.Notify(AUser: T);
var
ParametersAsTValueArray: array[1 .. 1] of TValue;
begin
ParametersAsTValueArray[1] := TValue.From<T>(AUser);
NotifyAll(TValueArrayToArrayOfConst(ParametersAsTValueArray));
end;
Notes:
TValueArrayToArrayOfConst()'s result is a non-owning container. It contains memory pointers backed by the source array of the TValue container. ParametersAsTValueArray is alive and not being altered while the array of const is used.
One of these 2 Rtti procedures has a bug with regard to TClass values processing. TClass becomes a Pointer on some stage, and string.Format() breaks because Pointer and TClass are not the same thing. Perform tests on all TVarRec.VType, they are not so many, much less that Variant's VType.

Delphi generics: How to spec "class that references its own type"

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
:-)

How to create a type that is string?

A blog entry from Raymond Chen today made me realize the elegant solution to a problem i'm having.
Various shell functions, rather than all taking ITEM­ID­LIST structure, can be made to only accept:
ITEM­ID_CHILD
ID­LIST_RELATIVE
ID­LIST_ABSOLUTE
ITEM­ID_CHILD_ARRAY
structures. The structures are all identical, but now you can enforce conceptual types at the compiler level.
Back to Delphi
i have a set of functions:
some only take a path: (e.g. C:\Users\Ian\Desktop\AllisonAngel.jpg)
some only take a filename: (e.g. AllisonAngel.jpg)
some only take a folder: (e.g. C:\Users\Ian\Desktop)
And right now they're all declared as string, e.g.:
function GetFilenames(Folder: string; ...): ...
function IsValidBatchFilename(Path: string): ...
function GetReportType(Filename: string): ...
and i have to trust that i'm passing the right stuff; and i'm trusting that developers (e.g. me), know the difference between:
a path
a filename
and a folder
i want to change the functions to use "typed" strings:
function GetFilenames(Folder: TFolderOnly; ...): ...
function IsValidBatchFilename(Path: TFullPath): ...
function GetReportType(Filename: TFilenameOnly): ...
Where:
type
TFullPath = type string;
TFolderOnly = type string;
TFilenameOnly = type string;
Except that there's no actual typing happening:
var
dropFolder: string;
begin
dropFolder := GetDropFolderPath(LCT);
GetFilenames(dropFolder); <-- no compile error
end;
What i want is a "distinct" type of string; that is string insofar that it is length prefixed, reference counted, null terminated.
You can use advanced records to accomplish this. For instance, you could do
type
TFileName = record
FFileName: string;
public
class function IsValidFileName(const S: string): boolean; static;
class operator Implicit(const S: string): TFileName;
class operator Implicit(const S: TFileName): string;
end;
implementation
class function TFileName.IsValidFileName(const S: string): boolean;
begin
result := true {TODO};
end;
class operator TFileName.Implicit(const S: string): TFileName;
begin
if IsValidFileName(S) then
result.FFileName := S
else
raise Exception.CreateFmt('Invalid file name: "%s"', [S]);
end;
class operator TFileName.Implicit(const S: TFileName): string;
begin
result := S.FFileName;
end;
and similarly for TPath and TFolder. Advantages:
A function expecting TPath will not accept a TFileName (or some other combination).
You can still assign a TPath to/from a regular string. If you cast from a string to a TPath, you will automatically check the string to see if it contains a valid path.
Optionally, you can specify how a TPath can be assigned to a TFileName (or some other combination), by writing more Implicit class operators.
Create different record types for each string type. Distinct record types are not assignment-compatible, even though string types are.
type
TFullPath = record value: string end;
TFolderOnly = record value: string end;
Chen's article compares the new shell feature to the classic STRICT macro that makes distinct handle types, and as I recall, declaring distinct structs is exactly how the STRICT macro works.
With the way Delphi handles basic types, I'm pretty this is a case of "you can't get there from here."
Your string type declarations are all going to satisfy Delphi's rules for type compatibility and assignment compatibility. What they will restrict is procedural declarations with var parameters. If you changed your function declarations to be var parameters instead of reference or value parameters, you would get a compile error in your final example.
All that said, it's all useless anyway. You still have no way to ensure that the input is only a filename, even with the TFilenameOnly type, and must test the contents in your procedures anyway.

How to overload a function based on the result type?

just a question, i have:
myclass = class
public
function Funct1: String;
function Funct2: Integer;
end;
It turn me error, so i have tried with:
myclass = class
public
function Funct1: String; overload;
function Funct2: Integer; overload;
end;
but same problem; delphi tell me that has same parameter.
Now, i ask, is possible to do in mode to have more function with same name but with different output as in example?
Thanks very much for help.
UPDATE
Sorry, i done a error, not funct1 and funct2, but both funct1, so:
myclass = class
public
function Funct1: String; overload;
function Funct1: Integer; overload;
end;
Doing so, compiler return me this error:
[DCC Error] Project1.dpr(15): E2252 Method 'funct1' with identical parameters already exists
[DCC Error] Project1.dpr(22): E2037 Declaration of 'funct1' differs from previous declaration
Of course, i know becouse give error and need change name to one of both function (for it me confused before) but i wanted know if there was some trick or other solution for to have a situation as this without error.
Thanks again.
You could turn the function into a procedure taking a var parameter:
myclass = class
public
procedure Funct1(var AResult: String); overload;
procedure Funct1(var AResult: Integer); overload;
end;
Firstly, for functions to be overloaded, they have to be named the same.
Secondly, this is not possible to do. Overloaded functions must have different parameters.
In your case, there is no way the compiler can tell which of your functions to call (assumed that both are renamed to Funct1):
var
v: Variant;
mc: myclass;
begin
v := mc.Funct1;
end;
What you post doesn't make sense. The first example should compile without any problem, as the functions have different names, Funct1 and Funct2.
Problems only arise when methods (or functions) have the same name. Then, normally, an overload directive would be in order, but overload can't distinguish functions on return value alone.
So assuming the names are the same, what you want is impossible. There is no way to overload these functions, if they don't have a different parameter signature. You can just give them different names, which is preferrable anyway, as they apparently do different things.
FWIW, your question is flawed by the fact that you apparently did not post the exact code with which you are actually having problems. Please always post the exact code that causes your problems, and if there are error messages, always post the exact error message (they can usually be copied using the usual copy keystrokes, e.g. Ctrl+C, even in most parts of the IDE or in message dialogs in Delphi). If there are any line numbers in the error message, indicate this in the source code you post, as we don't always have the same line numbers as you have.
if you want to overload methods with different return types, just add a dummy parameter with default value to allow the overload execution, but don't forget the parameter type should be different so the overload logic works e.g:
type
myclass = class
public
function Funct1(dummy: string = EmptyStr): String; overload;
function Funct1(dummy: Integer = -1): Integer; overload;
end;
use it like this
procedure tester;
var yourobject : myclass;
iValue: integer;
sValue: string;
begin
yourobject:= myclass.create;
iValue:= yourobject.Funct1(); //this will call the func with integer result
sValue:= yourobject.Funct1(); //this will call the func with string result
end;
also don't forget that this way your gonna have a problem with variants like in this example:
procedure tester;
var yourobject : myclass;
vValue: variant;
begin
yourobject:= myclass.create;
vValue:= yourobject.Funct1();
end;
until you implement the variant function too:
function Funct1(dummy: Variant = Unassigned): Variant; overload;
As was already stated, you cannot do it as you are wanting to do. However, you could just as easily implement each function with a different name such as "AsInteger" or "AsString". Your code will be clearer and this is generally the way it is done within the VCL.
TmyClass = class (TObject)
public
function AsString : string;
function AsInteger : Integer;
end;

Function & Procedures in Delphi (Defaults & Optional Parameters)

EDIT:
Is there a better way of doing this ?
TPendingBets = class(TDataModule)
private
public
function GetBdy(out IdEvent : Integer ) : Boolean; overload;
function GetBdy(out IdEvent : Integer; out idBetType : TBetTypes) : Boolean; overload;
function GetBdy(out IdEvent : Integer; out idBetType : TBetTypes; Out TotalOrgStake,Price : Double; out PriceError :Boolean): Boolean; overload;
end;
implementation
////////////////////
function TPendingBets.GetBdy(out IdEvent : Integer ): Boolean;
var idBetType : TBetTypes;
TotalOrgStake,Price : Double;
PriceError :Boolean;
begin
result := GetBdy(IdEvent,idBetType,TotalOrgStake,Price,PriceError);
end;
////////////////////
function TPendingBets.GetBdy(out IdEvent : Integer; out idBetType : TBetTypes): Boolean;
var TotalOrgStake,Price : Double;
PriceError :Boolean;
begin
result := GetBdy(IdEvent,idBetType,TotalOrgStake,Price,PriceError);
end;
////////////////////
function TPendingBets.GetBdy(out IdEvent : Integer; out idBetType : TBetTypes; Out TotalOrgStake,Price : Double; out PriceError :Boolean): Boolean;
begin
result := false;
if cdBdy.Eof = False then
begin
IdEvent := cdBdy.FieldByName(IdEvent ).AsInteger);
idBetType := TBetTypes(cdBdy.FieldByName(idBetType ).AsInteger);
//.
//.
result := True;
end;
end;
NOTE:
As my initial question was not very clear I have removed part of it
Overloading does not mean having 5 copies of the code in the function. You might have 5 functions, but 4 of them just call the main function with the correct parameters.
I generally don't like out parameters. Have you considered returning a record containing all the results?
Fn1Result = record
A: Extended;
B: Integer;
C, D, E, F: Extended
S: String;
end;
Then declare your function like:
function Fn1: Fn1Result;
begin
Fn1.A := C_ConstA;
// etc. . . .
end;
Naturally I am assuming you are not using output parameters as input. Per the Delphi help:
An out parameter, like a variable parameter, is passed by reference. With an out parameter, however, the initial value of the referenced variable is discarded by the routine it is passed to. The out parameter is for output only; that is, it tells the function or procedure where to store output, but doesn't provide any input.
I get a funny feeling you are using output parameters as input parameters. Don't. Pass them as var if you want them to go both ways.
I think the overload solution is quite a good solution, at least it is one. Other programming languages require you to declare functions with different names.
Another approach may be a dynamic array parameter like:
type
TExtendedDynArray = array of Extended; // if not already declared elsewhere
function Fn1(out arr: TExtendedDynArray): Boolean;
In addition to what Uwe wrote (and I agree that overloading is a better solution here; up voted), you should never presume an out parameter to have any value at all at the start of the function. That's what out means.
You can use overloaded function something like this
function Fn1(Out A:string; B:integer=5) : boolean; overload;
function Fn1(Out A:string) : boolean; overload;
you must to define both functions.
As Jim showed, you don't need to copy the code around when overloading.
I don't like much to mix parameters with default values and overloading, because (doing it without care) you can create a mess.
Normally, if I have to support a lot of syntaxes:
1) I define a complete signature, with ALL PARAMETERS. Let's call it the
'master' one.
2) All overloads call that one. Or call one to another, but in the end every
overload chain must call the 'master' one to execute the task.
For me, overloads are "parameter crunchers" or "translators". Later, if I need
something to modify the master behavior, I create new parameters on 'master' (which can have default values - in fact, mostly have).
If I need a overload with the new parameter, I create a new one and it simply pass it to
the "master" procedure. The side effect is that the old ones continue to behave the same.
Default values can only be used for input parameters (that is: By value and const). For var and out parameters, default values are not possible.
As Jim already pointed out: You can avoid duplicating the code by having most overloaded functions call the one which takes all parameters, so they are merely thin layers over the original function. From Delphi 2005 on you can declare them inline, so the compiler might actually generate more efficient code.

Resources