How to pass generic array as open array parameter in Delphi? - delphi

I have an enumerated type and I need to pass an array of this type as parameter:
type
TTest = (a,b,c);
procedure DoTest(stest: TArray<TTest>);
When I compile
DoTest([a]);
I receiv the error below:
Error: E2010 Incompatible types: 'System.TArray' and 'Set'*
So, how can I call DoTest without creating a variable of type TArray<TTest>?

I don't have a Delphi compiler available right now, so I cannot verify this, but to me
procedure DoTest(stest: TArray<TTest>);
doesn't declare stest as an open array parameter, but a dynamic array parameter. You do want
procedure DoTest(const stest: array of TTest);

One way to do what you want is to change the parameter to an open array of TTest, i.e.
procedure DoTest(const stest: array of TTest);
But supposed you don't want to change the parameter, and really want it to be a TArray<TTest>, then you can simply use the array pseudo-constructor syntax to call it (in almost all versions of Delphi, except the very old ones). Say you have something like:
type
TTest = (a, b, c);
procedure DoTest(const stest: TArray<TTest>);
// simple demo implementation
var
I: Integer;
begin
for I := Low(stest) to High(stest) do
Write(Integer(stest[I]), ' ');
Writeln;
end;
Then it can be called, using the Create syntax without having to declare a variable or having to fill it manually. The compiler will do this for you:
begin
DoTest(TArray<TTest>.Create(a, c, b, a, c));
end.
The output is, as expected:
0 2 1 0 2

The compiler may confuse a with another declaration.
Qualify the type like this:
DoTest([Ttest.a]);
Note:
This feature of initializing dynamic arrays was introduced in XE7.

I'm assuming that with "how can I call DoTest without creating a variable of type TArray" you want to avoid declaring and initializing local variable, ie code like
var arr: TArray<TTest>;
begin
SetLength(arr, 1);
arr[0] := a;
DoTest(arr);
For this you can use the array constructor like:
DoTest(TArray<TTest>.Create(a));
This syntaxs is supported at least since Delphi 2010.

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.

Generics and Array of const

type
TGeneric<T> = class(TBase)
public
procedure Swap(Harry : T);
procedure NotifyAll(AParams : T);
end;
i have two question
Is there any way i can typecast a generic variable in any other type like variant etc.
how can i pass generic type in function that accept array of const like here
Delphi - How can I pass Generic parameter to function that accept Array of const parameter
Is there any way I can typecast a generic variable in any other type like Variant etc.
As stated in my answer to the question you link, the variant type that works well with generics is TValue. You can make a TValue instance from a generic type using the From method:
procedure TMyClass.Foo<T>(const Item: T);
var
Value: TValue;
begin
Value := TValue.From<T>(Item);
end;
How can I pass generic type in function that accept array of const?
This is the exact same question that was asked in the question you linked to. The answer is the same as stated there, namely that you cannot pass a generic argument as a variant open array parameter. Again, TValue to the rescue. Instead of using a variant open array parameter, use an open array of TValue.
The bottom line here is that the variant type that works well with generics, and that is supplied with the Delphi RTL, is TValue.
Copying my code from linked question:
procedure TEventMulticaster<T>.Notify(AUser: T);
var
ParametersAsTValueArray: array[1 .. 1] of TValue;
begin
ParametersAsTValueArray[1] := TValue.From<T>(AUser);
NotifyAll(TValueArrayToArrayOfConst(ParametersAsTValueArray));
end;

Delphi: SetLength() on argument of type "array of TObject"

I'm trying to resize an array of a certain class passed as an argument, e.g.
procedure Resize(MyArray: Array of TObject);
begin
SetLength(MyArray, 100);
end;
However, this raises an error "E2008 Incompatible types". Is it true that you can't do this (I've seen rumors, but no official documentation) or am I doing something wrong?
You didn't defined the type explicitly. So the compiler has problems matching them. If you define the type like:
type
TObjectArray = array of TObject;
There is no doubt about it and (thanks to Mghie) you should be using a var parameter because resising likely cause a change in the pointer.
procedure Resize(var MyArray: TObjectArray);
begin
SetLength(MyArray, 100);
end;
You are mixing open arrays (the parameter of Resize) and dynamic arrays (what SetLength expects). See here for an explanation - especially the part titled "Confusion".

How to know what type is a var?

TypeInfo(Type) returns the info about the specified type, is there any way to know the typeinfo of a var?
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= TypeInfo(S);
Info:= TypeInfo(Instance);
Info:= TypeInfo(Obj);
end
This code returns:
[DCC Error] Unit1.pas(354): E2133 TYPEINFO standard function expects a type identifier
I know a non instantiated var is only a pointer address.
At compile time, the compiler parses and do the type safety check.
At run time, is there any way to know a little more about a var, only passing its address?
No.
First, there's no such thing as a "non-instantiated variable." You instantiate it by the mere act of typing its name and type into your source file.
Second, you already know all there is to know about a variable by looking at it in your source code. The variable ceases to exist once your program is compiled. After that, it's all just bits.
A pointer only has a type at compile time. At run time, everything that can be done to that address has already been determined. The compiler checks for that, as you already noted. Checking the type of a variable at run time is only useful in languages where a variable's type could change, as in dynamic languages. The closest Delphi comes to that is with its Variant type. The type of the variable is always Variant, but you can store many types of values in it. To find out what it holds, you can use the VarType function.
Any time you could want to use TypeInfo to get the type information of the type associated with a variable, you can also directly name the type you're interested in; if the variable is in scope, then you can go find its declaration and use the declared type in your call to TypeInfo.
If you want to pass an arbitrary address to a function and have that function discover the type information for itself, you're out of luck. You will instead need to pass the PTypeInfo value as an additional parameter. That's what all the built-in Delphi functions do. For example, when you call New on a pointer variable, the compiler inserts an additional parameter that holds the PTypeInfo value for the type you're allocating. When you call SetLength on a dynamic array, the compiler inserts a PTypeInfo value for the array type.
The answer that you gave suggests that you're looking for something other than what you asked for. Given your question, I thought you were looking for a hypothetical function that could satisfy this code:
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= GetVariableTypeInfo(#S);
Assert(Info = TypeInfo(string));
Info:= GetVariableTypeInfo(#Instance);
Assert(Info = TypeInfo(IObjectType));
Info:= GetVariableTypeInfo(#Obj);
Assert(Info = TypeInfo(TDBGrid));
end;
Let's use the IsClass and IsObject functions from the JCL to build that function:
function GetVariableTypeInfo(pvar: Pointer): PTypeInfo;
begin
if not Assigned(pvar) then
Result := nil
else if IsClass(PPointer(pvar)^) then
Result := PClass(pvar).ClassInfo
else if IsObject(PPointer(pvar)^) then
Result := PObject(pvar).ClassInfo
else
raise EUnknownResult.Create;
end;
It obviously won't work for S or Instance above, but let's see what happens with Obj:
Info := GetVariableTypeInfo(#Obj);
That should give an access violation. Obj has no value, so IsClass and IsObject both will be reading an unspecified memory address, probably not one that belongs to your process. You asked for a routine that would use a variable's address as its input, but the mere address isn't enough.
Now let's take a closer look at how IsClass and IsObject really behave. Those functions take an arbitrary value and check whether the value looks like it might be a value of the given kind, either object (instance) or class. Use it like this:
// This code will yield no assertion failures.
var
p: Pointer;
o: TObject;
a: array of Integer;
begin
p := TDBGrid;
Assert(IsClass(p));
p := TForm.Create(nil);
Assert(IsObject(p));
// So far, so good. Works just as expected.
// Now things get interesting:
Pointer(a) := p;
Assert(IsObject(a));
Pointer(a) := nil;
// A dynamic array is an object? Hmm.
o := nil;
try
IsObject(o);
Assert(False);
except
on e: TObject do
Assert(e is EAccessViolation);
end;
// The variable is clearly a TObject, but since it
// doesn't hold a reference to an object, IsObject
// can't check whether its class field looks like
// a valid class reference.
end;
Notice that the functions tell you nothing about the variables, only about the values they hold. I wouldn't really consider those functions, then, to answer the question of how to get type information about a variable.
Furthermore, you said that all you know about the variable is its address. The functions you found do not take the address of a variable. They take the value of a variable. Here's a demonstration:
var
c: TClass;
begin
c := TDBGrid;
Assert(IsClass(c));
Assert(not IsClass(#c)); // Address of variable
Assert(IsObject(#c)); // Address of variable is an object?
end;
You might object to how I'm abusing these functions by passing what's obviously garbage into them. But I think that's the only way it makes sense to talk about this topic. If you know you'll never have garbage values, then you don't need the function you're asking for anyway because you already know enough about your program to use real types for your variables.
Overall, you're asking the wrong question. Instead of asking how you determine the type of a variable or the type of a value in memory, you should be asking how you got yourself into the position where you don't already know the types of your variables and your data.
With generics, it is now possible to get the type info without specifying it.
Certain users indicated the following code doesn't compile without errors.
As of Delphi 10 Seattle, version 23.0.20618.2753, it compiles without errors, as seen below in the screenshot.
program TypeInfos;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.TypInfo;
type
TTypeInfo = class
class procedure ShowTypeInfo<T>(const X: T);
end;
{ TTypeInfo }
class procedure TTypeInfo.ShowTypeInfo<T>(const X: T);
var
LTypeInfo: PTypeInfo;
begin
LTypeInfo := TypeInfo(T);
WriteLn(LTypeInfo.Name);
end;
var
L: Exception;
B: Boolean;
begin
// Console output
TTypeInfo.ShowTypeInfo(L); // Exception
TTypeInfo.ShowTypeInfo(B); // Boolean
end.
Not that I know of. You can get RTTI (Run Time Type Information) on published properties of a class, but not for "normal" variables like strings and integers and so forth. The information is simply not there.
Besides, the only way you could pass a var without passing a type is to use either a generic TObject parameter, a generic type (D2008, as in ), or as an untyped parameter. I can't think of another way of passing it that would even compile.

Arrays as result type and input for functions

in delphi7 i have a function that i need to return a array as a result type b
"function createsbox(key:tkey):array[0..255] of byte;" this is not allowed it is expecting a "identifyer expected but array found" is the error throw up. seems to work fine if i declare a record type of array but it seems pointless to do this for one function.
The issue is that you're not allowed to create a new type in a function declaration. But that's what you're doing when you specify the return type as array[0..255] of Byte. Instead, declare a named type, and then use it for the return type:
type
TSBox = array[0..255] of Byte;
function CreateSBox(const Key: TKey): TSBox;
There is a subtle reason for that, and it is in Pascal two array type declarations are not the same "type" regardless of how identical their declaration are, and thereby not assignment compatible. If you write:
var
A: array[1..10] of Integer;
B: array[1..10] of Integer;
A and B are different types. If you write
A := B;
The code won't compile, A and B are different types.
Thereby, if you write
var
A: array[1..10] of Integer;
...
function Foo(...): array[1..10] of Integer;
You're actually declaring a type for the function result - that type would be pretty useless because you could not assign it to A or any array no matter how it s declaration is, for example:
A := Foo(...);
would not work even if the compiler would let you to declare a function that way.
The only way to have a useful function result type thereby is to use a type already declared. Only open arrays are an exception to this rule, but they can be used only as function parameters, not as the result.
okay type TSBox = array of Byte works fine, but using this new type in 2 or more units, could be tricky. 'Cause u will get an error message "incompatible types".
There's a partial solution for this situation. U can inform the compiler which unit has that type with this notation: unit.type;
Example:
Imagine there I have a unit called Web, where I declare the type TDownload. I could do something like this:
var fileUrl : Web.TDownload;
In this case the compiler will understand that u r using the same type.
However, and thats my question for u all: what if u don't want to put that unit in the uses clausule for whatever - circular reference, poo good practices, etc.. What can I do to avoid this problem?

Resources