Is it possible to pass to a function or procedure a dynamic array as optional parameter ? If yes, how ?
I have tried in this way :
procedure testp (str : string; var arr : StringArray = nil);
begin
str := 'Ciao Alessio !';
SetLength(arr, 2);
arr[0] := 'Ale';
arr[1] := 'Ale';
end;
but it gives : default parameter 'arr' must be by-value or const .
I'm using Delphi 7, but if not possible with Delphi 7, is it possible with newer version of Delphi or Free Pascal ?
Default parameters can only be specified for const or by value parameters. They cannot be specified for var parameters.
To achieve the caller flexibility you are looking for you'll need to use overloads.
procedure foo(var arr: StringArray); overload;
begin
.... do stuff
end;
procedure foo; overload;
var
arr: StringArray;
begin
foo(arr);
end;
The error message means exactly what it says, and it has nothing to do with the parameter being a dynamic array. The compiler would have rejected that code no matter what type the parameter had because you're not allowed to give default values for parameters passed by reference.
To make an optional reference parameter, use overloading to give two versions of the function. Change your current function to receive its parameter by value or const, as the compiler advises, and then declare another function without that parameter, as follows:
procedure testp (str : string);
var
arr: StringArray;
begin
testp(str, arr);
end;
That is, declare a dummy parameter and pass it to the "real" function. Then simply throw away the value it returns.
If calculation of the reference value is expensive, then the implementation of the one-parameter version of testp will instead duplicate more of the code from the two-argument version.
Related
I want to sort a TArray containing entries of a self defined TPair<>. I tried to follow this one, but the compiler always complains that he needs an object record or clastype (E2018):
How to sort a generic array containing records.
My code:
type
TFailureEntry = TPair<System.Word, TMyFailureRecord>;
procedure TMyClass.GetFailureAbbreviations;
var
FailureArray: TArray<TFailureEntry>;
Comparison: TComparison<TFailureEntry>;
begin
// derrive the array contents from a dictionary
FailureArray := FFailureDictionary.ToArray;
Comparison :=
function(const Left, Right: TFailureEntry): Integer
begin
Result := Left.Key-Right.Key;
end;
FailureArray.Sort(TComparer<TFailureEntry>.Construct(Comparison));
end;
The compiler complains at the .sort call.
An array cannot have a method - in this case no Sort method. Use TArray.Sort instead:
TArray.Sort<TFailureEntry>(FailureArray, TComparer<TFailureEntry>.Construct(Comparison));
I'm writing a kind of array wrapper, using a record as container, and including some "class-like" functions to it.
I also want the possibility to assign an array to it, as with a normal array, so I have implemented a Implicit class operator:
type
TArrayWrapper = record
class operator Implicit(AArray: array of TObject): TArrayWrapper; overload;
Items: TArray<TObject>;
procedure Add(AItem: TObject);
...
end;
So I can do things like:
procedure DoSomething;
var
myArray: TArrayWrapper;
begin
myArray := [Obj1, Obj2, Obj3];
...
end;
The problem appears when I try to pass an array of Integer to a method that has as parameter a TArrayWrapper:
procedure DoSomethingElse(AArrayWrapper: TArrayWrapper);
begin
...
end;
procedure DoSomething;
var
myArray: TArrayWrapper;
begin
myArray := [Obj1, Obj2, Obj3];
DoSomethingElse(myArray); <--- Works!!!!
DoSomethingElse([Obj1, Obj2, Obj3]); <--- Error E2001: Ordinal type required -> It is considering it like a set, not as an array
end;
What could be happening?
Thank you in advance.
The compiler has not implemented the string like operations on a dynamic array for class operators, when the record/class is used as a parameter.
There is not a QP report for this, as far as I can see. Now there is, see below.
A similar example is found in the comments here: Dynamic Arrays in Delphi XE7
A workaround:
DoSomethingElse(TArray<TObject>.Create(Obj1, Obj2, Obj3));
Or as #Stefan suggests, in order to avoid unnecessary allocations. Add a constructor to the record:
type
TArrayWrapper = record
class operator Implicit(AArray: array of TObject): TArrayWrapper;
constructor Init( const AArray: array of TObject);
end;
DoSomethingElse(TArrayWrapper.Init([obj1,obj2,obj3]));
Reported as: RSP-24610 Class operators do not accept dynamic arrays passed with brackets
I asked another question about using records with operators. During testing, I've discovered an anomaly where two instances of this type seem to share the same memory.
The record has an array of Integer...
type
TVersion = record
Values: array of Integer;
function Count: Integer;
class operator implicit(aVersion: TVersion): String;
class operator implicit(aVersion: String): TVersion;
end;
class operator TVersion.implicit(aVersion: TVersion): String;
var
X: Integer;
begin
Result:= '';
for X := 0 to Length(aVersion.Values)-1 do begin
if X > 0 then Result:= Result + '.';
Result:= Result + IntToStr(aVersion.Values[X]);
end;
end;
class operator TVersion.implicit(aVersion: String): TVersion;
var
S, T: String;
I: Integer;
begin
S:= aVersion + '.';
SetLength(Result.Values, 0);
while Length(S) > 0 do begin
I:= Pos('.', S);
T:= Copy(S, 1, I-1);
Delete(S, 1, I);
SetLength(Result.Values, Length(Result.Values)+1);
Result.Values[Length(Result.Values)-1]:= StrToIntDef(T, 0);
end;
end;
function TVersion.Count: Integer;
begin
Result:= Length(Values);
end;
Now I try to implement this...
var
V1, V2: TVersion;
begin
V1:= '1.2.3.4';
V2:= V1;
ShowMessage(V1);
ShowMessage(V2);
V2.Values[2]:= 8;
ShowMessage(V1);
ShowMessage(V2);
end;
I expect that the V2 should be 1.2.8.4 and V1 to remain 1.2.3.4. However, V1 also changes to 1.2.8.4.
How do I keep these records independent when I assign them?
This behaviour is by design. A dynamic array variable is a pointer. When you assign a dynamic array variable you take a copy of the pointer and increase the reference count. Of course this also happens when you assign compound structures that contain dynamic arrays.
The documentation covers this:
If X and Y are variables of the same dynamic-array type, X := Y points X to the same array as Y. (There is no need to allocate memory for X before performing this operation.) Unlike strings and static arrays, copy-on-write is not employed for dynamic arrays, so they are not automatically copied before they are written to.
To get a mental model for this, this of dynamic arrays as being references in the same way as classes and interfaces. This is in contrast to simple types (integer, double etc.), strings, records, etc.
The standard way to deal with this is as follows:
Make the dynamic array private.
Expose the contents of the array through properties.
In the element setter, make sure that the reference to the dynamic array is unique.
That last step is the tricky part. Make a dynamic array reference unique by calling SetLength:
SetLength(arr, Length(arr));
One of the promises that SetLength makes is that it will always make its first argument be unique.
This has the effect of implementing copy-on-write. To the best of my knowledge it is not possible to implement copy-on-assign because the compiler gives you no hook into the assignment operator.
So the answer to:
How do I keep these records independent when I assign them?
is that you cannot. You need to use copy-on-write instead.
I have a problem in Ada concurrent programming. I have to write a simple program in Ada that creates N of tasks of the same type, where N is the input from keyboard. The problem is that I have to know N before compilation...
I have tried to declare a separate type:
TYPE my_arr IS ARRAY(INTEGER RANGE <>) OF some_task;
and later in BEGIN section of main procedure:
DECLARE arr: my_arr(1 .. N);
but I get the error
unconstrained element type in array declaration
Which (I think) means that task type some_task is of unknown size.
Can anyone please help ?
This is a rewrite of the original answer, now that we know that the task type in
question is discriminated.
You are passing a value to each task via a 'discriminant' without a default, which makes the task type unconstrained; you can't declare an object of the type without supplying a value for the discriminant (and supplying a default wouldn't help, because once the object has been created the discriminant can't be changed).
One common approach to this uses access types:
with Ada.Integer_Text_IO;
with Ada.Text_IO;
procedure Maciek is
task type T (Param : Integer);
type T_P is access T;
type My_Arr is array (Integer range <>) of T_P;
task body T is
begin
Ada.Text_IO.Put_Line ("t" & Param'Img);
end T;
N, M : Integer;
begin
Ada.Text_IO.Put ("number of tasks: ");
Ada.Integer_Text_IO.Get (N);
Ada.Text_IO.Put ("parameter: ");
Ada.Integer_Text_IO.Get (M);
declare
-- Create an array of the required size and populate it with
-- newly allocated T's, each constrained by the input
-- parameter.
Arr : My_Arr (1 .. N) := (others => new T (Param => M));
begin
null;
end;
end Maciek;
You may need to deallocate the newed tasks once they are completed; in the code above, the tasks' memory is leaked on exit from the declare block.
Allocate them in a 'declare block', or dynamically allocate them:
type My_Arr is array (Integer range <>) of Some_Task;
type My_Arr_Ptr is access My_Arr;
Arr : My_Arr_Ptr;
begin
-- Get the value of N
-- Dynamic allocation
Arr := new My_Arr(1 .. N);
declare
Arr_On_Stack : My_Arr(1 .. N);
begin
-- Do stuff
end;
end;
If you are sure at runtime, how many tasks you want, you can pass that as command-line argument.
with Ada.Text_IO; use ADA.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Command_Line;
with Ada.Strings;
procedure multiple_task is
No_of_tasks : Integer := Integer'Value(Ada.Command_Line.Argument(1));
task type Simple_tasks;
task body Simple_tasks is
begin
Put_Line("I'm a simple task");
end Simple_tasks;
type task_array is array (Integer range <>) of Simple_tasks;
Array_1 : task_array(1 .. No_of_tasks);
begin
null;
end multiple_task;
And run as
> multiple_task.exe 3
I'm a simple task
I'm a simple task
I'm a simple task
Is something like this possible with Delphi? (with dynamic arrays of strings and records)
type
TStringArray = array of String;
TRecArray = array of TMyRecord;
procedure DoSomethingWithStrings(Strings : TStringArray);
procedure DoSomethingWithRecords(Records : TRecArray);
function BuildRecord(const Value : String) : TMyRecord;
DoSomethingWithStrings(['hello', 'world']);
DoSomethingWithRecords([BuildRecord('hello'), BuildRecord('world')]);
I know that it does not compile like that. Just wanted to ask if there's a trick to get something similar to that.
If you don't have to change the length of the arrays inside your DoSomethingWith* routines, I suggest using open arrays instead of dynamic ones, e.g. like this:
procedure DoSomethingWithStrings(const Strings: array of string);
var
i: Integer;
begin
for i := Low(Strings) to High(Strings) do
Writeln(Strings[i]);
end;
procedure DoSomethingWithRecords(const Records: array of TMyRecord);
var
i: Integer;
begin
for i := Low(Records) to High(Records) do
Writeln(Records[i].s);
end;
procedure Test;
begin
DoSomethingWithStrings(['hello', 'world']);
DoSomethingWithRecords([BuildRecord('hello'), BuildRecord('world')]);
end;
Please note the array of string in the parameter list - not TStringArray! See the article "Open array parameters and array of const", especially the section about "Confusion", for more information.