Computing: Free Pascal Programming

Variable number of arguments in Free Pascal functions/procedures.

As a difference with programming languages as for example C, Free Pascal functions and procedures have a fixed number of arguments. In ordinary situations that's not a big deal, but there are situations where the possibility to use a variable number of arguments in a function or procedure call would make things easier, or more convenient for the user of the program.

Suppose that we want to write a procedure that writes given values onto the screen, but not simple writes as the Pascal Write and Writeln, but using for each variable to be printed a given format, similar to the C function printf. This procedure would have two or more arguments, depending on the number of values that we actually want to display. Is it possible to implement such procedures in Free Pascal? It is - thanks to the special data type array of const.

Passing the arguments as an array of const.

Note: If all variable arguments to be passed to the function have the same data type, you can (and should) use an open array of this data type (array of Integer, array of string, ...) instead of the array of const.

Here is a simple program, that shows the declaration and usage of our WriteF procedure, as described above, but with an additional argument, allowing to add or not a CR+LF.

    program test;
    uses
        SysUtils;
    var
        I: Integer;
        S: string;
        Names: array[0..3] of string;
    // WriteF procedure
    procedure WriteF(Fmt: string; Args: array of const; LF: Boolean);
    begin
        Write(Format(Fmt, Args));
        if LF then
            Writeln;
    end;
    // Main program
    begin
        Writeln;
        Writeln('Formatted integers: Squares and cubes.');
        for I := 1 to 10 do
            WriteF('%2d %3d %4d', [I, I * I, I * I * I], True);
        Writeln;
        Writeln('Formatted real: Rounded Pi values.');
        S := 'Pi with 2 decimals is %0.2f - Pi with 3 decimals is %0.3f - Pi with 5 decimals is %0.5f.';
        WriteF(S, [Pi, Pi, Pi], True);
        Writeln;
        Writeln('Formatted strings: Right-alignment.');
        Names[0] := 'Aly'; Names[1] := 'Virginie'; Names[2] := 'Britt'; Names[3] := 'Alex';
        for I := 0 to 3 do
            WriteF('%8s', [Names[I]], True);
        Writeln; Write('ENTER to terminate '); Readln;
    end.

And here is a screenshot of the program output.

Free Pascal procedure with a variable number of arguments: Formatted output example

The second argument of our WriteF procedure is an array of const, i.e. an open array of constant values. This means that the array values are passed by value, in other words, for the procedure they are constants, thus they cannot be modified). The array values used in the calling program, on the other hand, may be constants or variables. The constant array in the procedure call is globally written as
    [var1, var2, ...]
where var1, var2, ... may be of any simple data type, in particular integer numbers, real numbers, and strings; structured data types (as arrays and records) may not be used as elements of an array of const.

As you probably noticed, the SysUtils function Format itself has an array of const as second argument. In fact, it is prototyped as
    function Format(const Fmt: string; const Args: array of Const): string;

As the elements of the array of const may have different data types, it's obvious that what effectively happens internally is a little bit more complex. In fact, the elements of an array of const are converted to a special variant record, with the type declaration as follows:

    type
        PVarRec = ^TVarRec;
        TVarRec = record
        case vType: PtrInt of
            vtInteger: (vInteger: Longint);
            vtBoolean: (vBoolean: Boolean);
            vtChar: (vChar: Char);
            vtWideChar: (vWideChar: WideChar);
            vtExtended: (vExtended: PExtended);
            vtString: (vString: PShortString);
            vtPointer: (vPointer: Pointer);
            vtPChar: (vPChar: PChar);
            vtObject: (vObject: TObject);
            vtClass: (vClass: TClass);
            vtPWideChar: (vPWideChar: PWideChar);
            vtAnsiString: (vAnsiString: Pointer);
            vtCurrency: (vCurrency: PCurrency);
            vtVariant: (vVariant: PVariant);
            vtInterface: (vInterface: Pointer);
            vtWideString: (vWideString: Pointer);
            vtInt64: (vInt64: PInt64);
            vtQWord: (vQWord: PQWord);
        end;

Inside the procedure body, the array of const argument is equivalent to an open array of TVarRec, as shown in the following code example.

    program test;
    // Display info about array of const elements
    procedure Arguments(Args: array of const);
    const
        vtString = 11;
        vtFloat = 3;
    var
        L, I: Integer;
    begin
        L := Length(Args);
        if L = 0 then
            Writeln(' You have called the function without arguments.')
        else begin
            Writeln(' You have called the function with ', Length(Args), ' arguments:');
            for I := 0 to Length(Args) - 1 do begin
                Write(' Argument ', I + 1, ': ');
                case Args[I].vType of
                    vtInteger: Writeln ('Integer; value = ', Args[I].vInteger);
                    vtFloat: Writeln ('Real; value = ?');
                    vtString: Writeln ('string; value = ', String(Args[I].vString));
                    vtChar: Writeln ('Char; value = ', Args[I].vChar);
                    vtBoolean: Writeln ('Boolean; value = ', Args[I].vBoolean);
                    else Writeln ('Other (', Args[I].vType, '); value = ?');
                end;
            end;
        end;
    end;
    // Main program
    begin
        Writeln;
        Writeln('Parameters: None'); Arguments([]);
        Writeln('Parameters: A, B, 25'); Arguments(['A', 'B', 25]);
        Writeln('Parameters: ''Hi, there!'''); Arguments(['Hi, there!']);
        Writeln('Parameters: 1.2, 2.4'); Arguments([1.2, 2.4]);
        Writeln('Parameters: True, False'); Arguments([True, False]);
        Writeln('Parameters: ''Totals are'', 2888, 2222.22'); Arguments(['Totals are', 2888, 2222.22]);
        Writeln('Parameters: @Arguments, NIL'); Arguments([@Arguments, nil]);
        Writeln; Write('ENTER to terminate '); Readln;
    end.

And here is a screenshot of the program output.

Free Pascal procedure with a variable number of arguments: Get TVarRec item values

As the elements of an array of const are records, we cannot access their value by simply specifying Args[I]. Instead, we have to add the record item corresponding to the value's data type; e.g. Args[I].vInteger for an Integer value. When I played around with my array of const, I noticed that the vtString case was not triggered by a string argument. Redefining vtString by setting vType = 11, solved this issue, and I succeeded to access the value of the string using String(Args[I].vString). How to access decimal numbers, on the other hand, I have no idea! I found out that Real and Double arguments trigger the case vType = 3. But, there isn't any record item for decimal values in the TVarRec type declaration. So, how to do to get the actual value? Or, doesn't an array of const not really work with elements of data type Real/Double?

Defining defaults for optional arguments.

Free Pascal allows to specify a default value for arguments when declaring a function/procedure. Such arguments will be optional, i.e. they don't need to be specified when calling the routine. Lets look at an example. Suppose that we want to write a function that calculates the hydrostatic pressure of a given liquid at a given depth. The formula for this is: p = gρh, where p is the pressure, ρ the liquid's density, and g the acceleration due to gravity. In this equation, g may be considered as a constant, and we could hard-code its value within the function definition. However, giving the user the possibility to specify a value for g when calling the function also has its advantages. When comparing the results of the functions with some exercises in a physics book, the book results depend on the value of g actually used. In Western Europe, we normally use g = 9.81 m/s², in USA they mostly use g = 9.8 m/s², I think, and in lots of exercises, in order to simplify the calculations, g = 10 m/s² is used. We could thus write our function as follows:

    function HPressure(Density, Hight: Real; G: Real = 9.81): Real;
    begin
        Result := G * Density * Hight;
    end;

And we can call the function with either 2, or 3 arguments (all variables being reals; D, H, and G being initialized). If only 2 arguments are specified, the default value G = 9.81 will be used.

    P := HPressure(D, H);
    P := HPressure(D, H, G);

Using overloaded functions/procedures.

Function/procedure names are unique, but Free Pascal allows to define a routine several times with the same name, declaring them as overloaded. Lets take this simple example. Suppose that we want to write a function that calculates the surface of a rectangle. This function normally has two arguments: its length a and its width b. In the case of a square, a = b, thus one argument is enough to calculate the surface. Defining the function as something like function Surface(A: Real; B: Real = A): Real; is invalid; default values have to be constant literals. Thus, lets define the function twice, once with 2 arguments (general case), and once with 1 argument (square).

    function Surface(A, B: Real): Real; overload;
    begin
        Result := A * B;
    end;

    function Surface(A: Real): Real; overload;
    begin
        Result := Sqr(A);
    end;

And we can call the function with either 1, or 2 arguments (all variables being initialized reals).

    Writeln(Surface(Length, Width));
    Writeln(Surface(Side));


If you find this text helpful, please, support me and this website by signing my guestbook.