8.8 A word about scope

It should be stressed that all identifiers other than the template placeholders should be known when the generic class is declared. This works in 2 ways. First, all types must be known, that is, a type identifier with the same name must exist. The following unit will produce an error:

{$mode objfpc}  
unit myunit;  
 
interface  
 
type  
  Generic TMyClass<T> = Class(TObject)  
    Procedure DoSomething(A : T; B : TSomeType);  
  end;  
 
Type  
  TSomeType = Integer;  
  TSomeTypeClass = specialize TMyClass<TSomeType>;  
 
Implementation  
 
Procedure TMyClass.DoSomething(A : T; B : TSomeType);  
 
begin  
  // Some code.  
end;  
 
end.

The above code will result in an error, because the type TSomeType is not known when the declaration is parsed:

home: >fpc myunit.pp  
myunit.pp(8,47) Error: Identifier not found "TSomeType"  
myunit.pp(11,1) Fatal: There were 1 errors compiling module, stopping

The second way in which this is visible, is the following. Assume a unit

{$mode objfpc}  
unit mya;  
 
interface  
 
type  
  Generic TMyClass<T> = Class(TObject)  
    Procedure DoSomething(A : T);  
  end;  
 
 
Implementation  
 
Procedure DoLocalThings;  
 
begin  
  Writeln(’mya.DoLocalThings’);  
end;  
 
 
Procedure TMyClass.DoSomething(A : T);  
 
begin  
  DoLocalThings;  
end;  
 
end.

The compiler will not allow to compile this unit, since the DoLocalThings function will not be visible when the generic type is specialized:

Error: Global Generic template references static symtable

Now, if the unit is modified, and the DoLocalThings function is moved to the interface section, the unit will compile. When using this generic in a program:

{$mode objfpc}  
program myb;  
 
uses mya;  
 
procedure DoLocalThings;  
 
begin  
  Writeln(’myb.DoLocalThings’);  
end;  
 
Type  
  TB = specialize TMyClass<Integer>;  
 
Var  
  B : TB;  
 
begin  
  B:=TB.Create;  
  B.DoSomething(1);  
end.

Despite the fact that generics act as a macro which is replayed at specialization time, the reference to DoLocalThings is resolved when TMyClass is defined, not when TB is defined. This means that the output of the program is:

home: >fpc -S2 myb.pp  
home: >myb  
mya.DoLocalThings

This behaviour is dictated by safety and necessity:

  1. A programmer specializing a class has no way of knowing which local procedures are used, so he cannot accidentally ’override’ it.
  2. A programmer specializing a class has no way of knowing which local procedures are used, so he cannot implement it either, since he does not know the parameters.
  3. If implementation procedures are used as in the example above, they cannot be referenced from outside the unit. They could be in another unit altogether, and the programmer has no way of knowing he should include them before specializing his class.