Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Generic Functions

A function can declare type parameters. A type parameter stands for the type that the call uses, and it has a constraint that says which types are allowed.

FUNCTION MAX <T: ANY_ELEMENTARY>: T
    VAR_INPUT
        in1: T;
        in2: T;
    END_VAR
END_FUNCTION

A type parameter can be the type of an input, of an output, and of the return value.

How a call is resolved

A generic function is never called. Every call resolves to a concrete version, and the name of that version is the name of the function and then, for each type parameter, two underscores and the resolved type:

x := MAX(aDint, bDint);   (* calls MAX__DINT *)

The compiler does not write the body of that version. Either a FUNCTION with the resolved name provides it, written in Structured Text, or an object file or library that the linker finds does, for example one written in C.

FUNCTION MAX__DINT: DINT
    VAR_INPUT
        in1: DINT;
        in2: DINT;
    END_VAR

    IF in1 > in2 THEN
        MAX__DINT := in1;
    ELSE
        MAX__DINT := in2;
    END_IF
END_FUNCTION

When nothing in the project defines the name, the compiler writes an {external} declaration for it and leaves the symbol to the linker. If the linker finds nothing either, the build fails with an undefined symbol.

The generic functions that the compiler knows itself work differently. A call to ABS, ADD, or SEL gets no version of its own, because the compiler writes the code at the place of the call.

Constraints

The constraint is a type nature, for example ANY_INT, ANY_REAL, ANY_ELEMENTARY, or ANY. An argument whose type does not have that nature is rejected:

FUNCTION Inc <T: ANY_INT>: T
    VAR_INPUT
        v: T;
    END_VAR
END_FUNCTION

FUNCTION main: DINT
    VAR
        value: REAL := 1.0;
    END_VAR

    (* error[E062]: Invalid type nature for generic argument. REAL is no ANY_INT *)
    main := Inc(value);
END_FUNCTION

An integer argument for an ANY_REAL constraint is the one exception. The compiler converts it to a floating-point type and resolves the call with that type, so a <T: ANY_REAL> function called with a DINT resolves to its REAL version.

ANY accepts every type, so a call with a type that has no implementation passes the compiler and fails at the link step:

TYPE Point:
    STRUCT
        x, y: DINT;
    END_STRUCT
END_TYPE

FUNCTION main: DINT
    VAR
        s: STRING;
        p: Point;
    END_VAR

    s := TO_STRING(p);   (* undefined symbol TO_STRING__Point at link time *)
END_FUNCTION

The standard library uses this for its conversions. TO_STRING declares <T: ANY> and the library provides an implementation for every type that it supports, such as TO_STRING__DINT and TO_STRING__REAL.

The natures that a constraint can name are ANY, ANY_DERIVED, ANY_ELEMENTARY, ANY_MAGNITUDE, ANY_NUM, ANY_REAL, ANY_INT, ANY_SIGNED, ANY_UNSIGNED, ANY_DURATION, ANY_BIT, ANY_CHARS, ANY_STRING, ANY_CHAR, and ANY_DATE. Any other name is rejected. They form a tree with ANY at the root: ANY_SIGNED is part of ANY_INT, ANY_INT is part of ANY_NUM, ANY_NUM is part of ANY_MAGNITUDE, and ANY_MAGNITUDE is part of ANY_ELEMENTARY. A constraint accepts every type below it, so ANY_INT takes a DINT and also a UDINT.

What’s next

The next chapter is about working with addresses: pointers and references.