Difference between revisions of "for-in loop"

From Free Pascal wiki
Jump to navigationJump to search
(Use provided NativeUInt instead of IFDEF types)
 
(39 intermediate revisions by 14 users not shown)
Line 1: Line 1:
 
{{for-in loop}}
 
{{for-in loop}}
  
"for-in" loop exists in delphi starting from 2005 version. It is implemented now in FPC 2.4.2.
+
This iterates over a collection, but where a [[For|basic for-loop]] uses a numerical index counter, a for-in loop instead retrieves collection elements into the counter variable for immediate use. For-in works on strings, arrays, sets, and any other custom collection that implements the required iterators. Looping over an empty collection does nothing. The counter variable can not be modified inside the loop.
 +
 
 +
The '''for-in loop''' construct is supported in Delphi from Delphi 2005 onwards. It was implemented in FPC 2.4.2.
 +
 
 +
The official documentation is here: [https://www.freepascal.org/docs-html/ref/refsu58.html Reference guide chapter 13]
 +
 
  
 
== Delphi and FPC implementation ==
 
== Delphi and FPC implementation ==
  
It has the next syntax:
+
A '''for in''' loop has the following syntax:
  
 
=== String loop ===
 
=== String loop ===
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure StringLoop(S: String);
 
procedure StringLoop(S: String);
 
var
 
var
Line 17: Line 22:
 
     DoSomething(C);
 
     DoSomething(C);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Array loop ===
 
=== Array loop ===
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure ArrayLoop(A: Array of Byte);
 
procedure ArrayLoop(A: Array of Byte);
 
var
 
var
Line 29: Line 34:
 
     DoSomething(B);
 
     DoSomething(B);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Set loop ===
 
=== Set loop ===
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TColor = (cRed, cGren, cBlue);
 
   TColor = (cRed, cGren, cBlue);
Line 44: Line 49:
 
     DoSomething(Color);
 
     DoSomething(Color);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
 +
 
 +
=== Loop variables are temporary copies of the container value ===
 +
The loop variable in a for-in loop is a [https://stackoverflow.com/questions/18575736/delphi-dynamic-array-iteration-and-record-copyingsimple copy of the value that is stored inside a container over which the for-in loop is performed].
 +
 
 +
<syntaxhighlight lang=pascal>
 +
program tempcopy;
 +
 
 +
{$IFDEF FPC}
 +
  {$mode Delphi}
 +
{$ENDIF}
 +
 
 +
uses SysUtils;
 +
 
 +
type
 +
  PointerAddress = NativeUInt;
 +
 
 +
var
 +
  pIntArr: Pointer;
 +
  IntArr: Array of Integer;
 +
 
 +
procedure ArrayLoop(const AArr: Array of Integer);
 +
var
 +
  i: Integer;
 +
begin
 +
  pIntArr := @AArr;
 +
  writeln('Argument: ', PointerAddress(pIntArr)); // <-- same memory address as Item 1
 +
  pIntArr := @i;
 +
  writeln('Local loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i
 +
  for i in AArr do
 +
  begin
 +
    pIntArr := @i;
 +
    writeln('Loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i
 +
  end;
 +
end;
 +
 
 +
begin
 +
  // allow 3 items in the array
 +
  SetLength(IntArr, 3);
 +
 
 +
  pIntArr := @pIntArr;
 +
  writeln('global pIntArr address: ', PointerAddress(pIntArr));
 +
 
 +
  // IntArr is separate variable that points to first element
 +
  pIntArr := @IntArr;
 +
  writeln('global IntArr address: ', PointerAddress(pIntArr));
 +
  pIntArr := @IntArr;
 +
  writeln('IntArr points to: ', PointerAddress(pIntArr^));
 +
 
 +
  // addresses of the single items
 +
  pIntArr := @IntArr[0];
 +
  writeln('Item 1: ', PointerAddress(pIntArr)); // <-- memory address of the first item
 +
  pIntArr := @IntArr[1];
 +
  writeln('Item 2: ', PointerAddress(pIntArr));
 +
  pIntArr := @IntArr[2];
 +
  writeln('Item 3: ', PointerAddress(pIntArr));
 +
 
 +
  writeln('array loop');
 +
  ArrayLoop(IntArr);
 +
end.
 +
</syntaxhighlight>
  
 
=== Traversing container ===
 
=== Traversing container ===
  
To traverse some container class you need to add an <b>enumerator</b> for it. <b>Enumerator</b> is a class built by the next template:
+
To traverse a container class you need to add an <b>enumerator</b> for it. An <b>Enumerator</b> is a class structured according to the following template:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
TSomeEnumerator = class
 
TSomeEnumerator = class
 
public
 
public
Line 56: Line 121:
 
   property Current: TSomeType;
 
   property Current: TSomeType;
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
There are only 2 things required for the enumerator: MoveNext method which asks enumerator to step forward and property Current which can return any desired type.
+
There are only 2 things required for the enumerator class: a <b>MoveNext</b> method which asks the enumerator to step forward and a '''Current''' property which can return any appropriate type.
  
Next thing is to add magic <b>GetEnumerator</b> method to the container class which returns an <b>enumerator</b> instance.
+
Thereafter you need to add the magic <b>GetEnumerator</b> method to the container class which returns an <b>enumerator</b> instance.
  
 
For example:
 
For example:
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TEnumerableTree = class;
 
   TEnumerableTree = class;
Line 102: Line 167:
 
begin
 
begin
 
   Result := TTreeEnumerator.Create(Self);
 
   Result := TTreeEnumerator.Create(Self);
 +
  // Note: the Result is automatically freed by the compiler after the loop.
 
end;
 
end;
  
</delphi>
+
</syntaxhighlight>
  
After this you are able to execute the next code:
+
After this you are able to execute the following code:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure TreeLoop(ATree: TEnumerableTree);
 
procedure TreeLoop(ATree: TEnumerableTree);
 
var
 
var
Line 116: Line 182:
 
     DoSomething(ANode);
 
     DoSomething(ANode);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
Of course enumerator support is built into the basic classes: TList, TStrings, TCollection, TComponent, ...
+
You will find that several basic classes (such as TList, TStrings, TCollection, TComponent ...) already have built-in enumerator support.
  
It is also possible to make some class enumerable if you implement the next interface for the container:
+
It is also possible to make any class enumerable if you implement the following interface in your enumerable container class:
<delphi>
+
 
 +
<syntaxhighlight lang=pascal>
 
   IEnumerable = interface(IInterface)
 
   IEnumerable = interface(IInterface)
 
     function GetEnumerator: IEnumerator;
 
     function GetEnumerator: IEnumerator;
 
   end;
 
   end;
</delphi>
+
</syntaxhighlight>
  
 
Where <b>IEnumerator</b> is declared as:
 
Where <b>IEnumerator</b> is declared as:
<delphi>
+
 
 +
<syntaxhighlight lang=pascal>
 
   IEnumerator = interface(IInterface)
 
   IEnumerator = interface(IInterface)
 
     function GetCurrent: TObject;
 
     function GetCurrent: TObject;
Line 135: Line 203:
 
     property Current: TObject read GetCurrent;
 
     property Current: TObject read GetCurrent;
 
   end;
 
   end;
</delphi>
+
</syntaxhighlight>
 +
 
 +
=== Multiple enumerators for one class ===
 +
 
 +
You can add additional enumerators to classes, objects and records.
 +
 
 +
Here is an example of adding an enumerator which traverses a TEnumerableTree in reverse order:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
type
 +
  TEnumerableTree = class;
 +
 
 +
  TTreeEnumerator = class
 +
  ...for traversing in order, see above...
 +
  end;
 +
 
 +
  TTreeReverseEnumerator = class
 +
  private
 +
    FTree: TEnumerableTree;
 +
    FCurrent: TNode;
 +
  public
 +
    constructor Create(ATree: TEnumerableTree);
 +
    function MoveNext: Boolean;
 +
    property Current: TNode read FCurrent;
 +
    function GetEnumerator: TTreeReverseEnumerator; // returns itself
 +
  end;
 +
 
 +
  TEnumerableTree = class
 +
  public
 +
    function GetEnumerator: TTreeEnumerator;
 +
    function GetReverseEnumerator: TTreeReverseEnumerator;
 +
  end;
 +
 
 +
...see above for an implementation of the TTreeEnumerator...
 +
 
 +
constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
 +
begin
 +
  inherited Create;
 +
  FTree := ATree;
 +
end;
 +
 
 +
function TTreeReverseEnumerator.MoveNext: Boolean;
 +
begin
 +
  // some logic to get the next node from a tree in reverse order
 +
  if FCurrent = nil then
 +
    FCurrent := FTree.GetLastNode
 +
  else
 +
    FCurrent := FTree.GetPrevNode(FCurrent);
 +
  Result := FCurrent <> nil;
 +
end;
 +
 
 +
function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
 +
begin
 +
  Result := Self;
 +
end;
 +
 
 +
function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
 +
begin
 +
  Result := TTreeReverseEnumerator.Create(Self);
 +
  // Note: the Result is freed automatically by the compiler after the loop.
 +
end;
 +
</syntaxhighlight>
 +
 
 +
After this you are able to execute the following code:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TreeLoop(ATree: TEnumerableTree);
 +
var
 +
  ANode: TNode;
 +
begin
 +
  for ANode in ATree.GetReverseEnumerator do
 +
    DoSomething(ANode);
 +
end;
 +
</syntaxhighlight>
  
 
== FPC extensions ==
 
== FPC extensions ==
The following examples are not supported by Delphi, and implemented in FPC only.
+
The following code examples illustrate constructs that are implemented only by FPC, constructs which are not supported by Delphi.
  
=== Traverse enumerations and ranges ===
+
=== Traversing enumeration and subrange types ===
  
In Delphi, it is impossible to traverse neither enumerated nor ranged type:
+
In Delphi, it is [http://qc.embarcadero.com/wc/qcmain.aspx?d=106212 not possible to traverse either enumerated types] or subrange types, whereas in Free Pascal we can write the following:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TColor = (clRed, clBlue, clBlack);
 
   TColor = (clRed, clBlue, clBlack);
Line 157: Line 298:
 
     DoSomethingOther(ch);
 
     DoSomethingOther(ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
 +
 
 +
An example of this can further be demonstrated by the following code taken from bugreport 0029147, where the type system is disabled because a hardcast is used on a value that is not necessarily part of the enum value:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
type
 +
  TSomeEnums = (One, Two, Three);
 +
 
 +
resourcestring
 +
  SOne = 'One ape';
 +
  STwo = 'Two apes';
 +
  SThree = 'Three apes';
 +
 
 +
const
 +
  SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
 +
    SOne, STwo, SThree);
 +
 
 +
var
 +
  i: Integer;
 +
  SE: TSomeEnums;
 +
 
 +
begin
 +
  for i := 0 to 4 do begin
 +
    SE := TSomeEnums(i);  // hardcast. i can be higher than 2, but the type system is now disabled
 +
    WriteLn(SSomeEnumStrings[SE]);
 +
  end;
 +
end.
 +
</syntaxhighlight>
 +
The programmer instructs the compiler that i is of the enum type and the compiler will not check any further, assuming that the programmer knows best.<br>
 +
Of course the programmer is wrong and the compiler would have known better...<br>
 +
By traversing the enumerated type with '''for in do''' the hard cast is superfluous and the code becomes type safe:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
type
 +
  TSomeEnums = (One, Two, Three);
 +
 
 +
resourcestring
 +
  SOne  = 'One ape';
 +
  STwo  = 'Two apes';
 +
  SThree = 'Three apes';
 +
 
 +
const
 +
  SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
 +
    SOne, STwo, SThree);
 +
 
 +
var
 +
  SE: TSomeEnums;
 +
begin
 +
    for SE in TSomeEnums do
 +
      WriteLn(SSomeEnumStrings[SE]);
 +
end.</syntaxhighlight>
  
 
=== Declaring enumerators ===
 
=== Declaring enumerators ===
  
It is also not possible in Delphi to add an enumerator without modifying the class, as well as add an enumerator to the non-class/object/record/interface type.
+
It is also not possible in Delphi to add an enumerator without modifying the class, nor to add an enumerator to the non-class/object/record/interface type.
It is possible in FPC by adding new <b>operator</b> type <b>Enumerator</b>. Like in the next example:
+
FPC makes this possible using the new syntax <b>operator</b> type <b>Enumerator</b>. As in the following example:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TMyRecord = record F1: Integer; F2: array of TMyType; end;
 
   TMyRecord = record F1: Integer; F2: array of TMyType; end;
Line 189: Line 380:
 
     DoSomething(V);
 
     DoSomething(V);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
As a particularly useful example, the above extension would allow
+
==== Traversing UTF-8 strings ====
to traverse UTF-8 strings efficiently:
+
As a particularly useful example, the above extension allows
 +
very efficient traversal of UTF-8 strings:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 +
uses
 +
  LazUTF8;
 +
interface
 
type
 
type
   TUTF8StringEnumerator = class
+
   { TUTF8StringAsStringEnumerator
 +
    Traversing UTF8 codepoints as strings is useful when you want to know the
 +
    exact encoding of the UTF8 character or if you like to use string
 +
    constants in your code.
 +
    For security reasons you should use the codepoints values (cardinals) instead.
 +
    If speed matters, don't use enumerators. Instead use the PChar directly as
 +
    shown in the MoveNext method and read about UTF8. It has some interesting features. }
 +
 
 +
  TUTF8StringAsStringEnumerator = class
 
   private
 
   private
     FByteIndex: Integer;
+
     fCurrent: UTF8String;
     FCharIndex: Integer;
+
     fCurrentPos, fEndPos: PChar;
     function GetCurrent: UTF8Char;
+
     function GetCurrent: UTF8String;
 
   public
 
   public
 
     constructor Create(const A: UTF8String);
 
     constructor Create(const A: UTF8String);
     property Current: UTF8Char read GetCurrent;
+
     property Current: UTF8String read GetCurrent;
 
     function MoveNext: Boolean;
 
     function MoveNext: Boolean;
 
   end;
 
   end;
  
   operator Enumerator(A: UTF8String): TUTF8StringEnumerator;
+
   operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
 +
 
 +
var
 +
  Form1: TForm1;
 +
 
 +
implementation
 +
 
 +
operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
 +
begin
 +
  Result := TUTF8StringAsStringEnumerator.Create(A);
 +
end;
 +
 
 +
{ TUTF8StringAsStringEnumerator }
 +
 
 +
function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
 +
begin
 +
  Result:=fCurrent;
 +
end;
 +
 
 +
constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
 +
begin
 +
  fCurrentPos:=PChar(A); // Note: if A='' then PChar(A) returns a pointer to a #0 string
 +
  fEndPos:=fCurrentPos+length(A);
 +
end;
 +
 
 +
function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
 +
var
 +
  l: Integer;
 +
begin
 +
  if fCurrentPos<fEndPos then
 
   begin
 
   begin
     Result := TUTF8StringEnumerator.Create(A);
+
     l:=UTF8CharacterLength(fCurrentPos);
   end;
+
    SetLength(fCurrent,l);
 +
    Move(fCurrentPos^,fCurrent[1],l);
 +
    inc(fCurrentPos,l);
 +
    Result:=true;
 +
   end else
 +
    Result:=false;
 +
end;
 +
 
 +
{ TForm1 }
  
 +
procedure TForm1.FormCreate(Sender: TObject);
 
var
 
var
   s: UTF8String;
+
   s, ch: UTF8String;
  ch: UTF8Char;
+
   i: SizeInt;
   i: Integer;
 
 
begin
 
begin
   // This requires O(N^2) operations
+
  s:='mäßig';
   for i := 1 to Length(s) do
+
 
     DoSomething(ch[i]);
+
   // using UTF8Length and UTF8Copy this way is slow, requiring O(n)^2
   // This requires only O(N) operations
+
   for i:=1 to UTF8Length(s) do
 +
     writeln('ch=',UTF8Copy(s,i,1));
 +
 
 +
   // using the above enumerator is shorter and quite fast, requiring O(n)
 
   for ch in s do
 
   for ch in s do
     DoSomething(ch);
+
     writeln('ch=',ch);
end.
+
end;
</delphi>
+
</syntaxhighlight>
  
 
=== Using any identifiers instead of builtin MoveNext and Current ===
 
=== Using any identifiers instead of builtin MoveNext and Current ===
In Delphi you must use a function with the name 'MoveNext' and a property with the name 'Current' in enumerators. With FPC you can choose whatever names you wish. This is solved this by using 'enumerator MoveNext' and 'enumerator Current' modifiers. Like in the next example:
+
In Delphi you must use a function with the name 'MoveNext' and a property with the name 'Current' in enumerators. With FPC you can choose whatever names you wish. This is enabled by the use of the '''enumerator''' modifier, with the syntax 'enumerator MoveNext;' and 'enumerator Current;' modifiers. As in the following example:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   { TMyListEnumerator }
 
   { TMyListEnumerator }
Line 278: Line 521:
 
   List.Free;
 
   List.Free;
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
== Proposed extensions ==
 
== Proposed extensions ==
=== Select which enumerator to use ===
 
It is impossible to choose among different possible enumerators. For example you can traverse a tree using different orders. The well known algorithms are: preorder, postorder, inorder and breadth‑first traversals. Therefore it would be useful to have an ability to choose an enumerator. For example using the following syntax:
 
  
<delphi>
 
type
 
  TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)
 
procedure TraverseTree(Tree: TTree);
 
var
 
  Node: TNode;
 
begin
 
  // Variant1. For the class instances we can call the method Tree.GetEnumerator(teInOrder).
 
  // For the classes we can call a class method
 
  for Node in Tree using GetEnumerator(teInOrder) do
 
    Dosomething(Node);
 
 
  // Variant2. Or we can call the global function
 
  for Node in Tree using GetEnumerator(Tree, teInOrder) do
 
    Dosomething(Node);
 
 
  // Variant3. In the previous variant 'in Tree' is useless so the next code is a simplified form:
 
  for Node using GetEnumerator(Tree, teInOrder) do
 
    Dosomething(Node);
 
 
  // Variant4. We can try to avoid new context key-word 'using' by calling method:
 
  for Node in Tree.GetSomeEnumerator(teInOrder) do
 
    Dosomething(Node);
 
  // but this brings ambiguity to the compiler since Tree.GetSomeEnumerator(teInOrder) can be translated into
 
  // Tree.GetSomeEnumerator(teInOrder).GetEnumerator
 
  // This ambiguity might be resolvable by checking whether the class implements IEnumerator interface
 
end;
 
 
// for basic type we will call only the apropriate function
 
procedure TraverseString(S: String);
 
var
 
  C: Char;
 
begin
 
  for C in S using GetReverseStringEnumerator(S) do
 
    DoSomething(C);
 
end;
 
</delphi>
 
  
 
=== Get enumerator Position if available ===
 
=== Get enumerator Position if available ===
Finally, it is impossible to extract any information from the iterator
+
It is impossible to extract any information from the iterator
except the current item. Sometimes other data, such as current index, may be useful:
+
except the current item. Sometimes other data, such as the current index, might be useful:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TUTF8StringEnumerator = class
 
   TUTF8StringEnumerator = class
Line 363: Line 567:
 
   // Proposed extension
 
   // Proposed extension
 
   for ch in s index i do
 
   for ch in s index i do
 +
    Writeln(i, ': ', ch);
 +
 +
  // Proposed extension for traversing backwards (equivalent to downto)
 +
  for ch in reverse s do
 +
    Writeln(i, ': ', ch);
 +
 +
  // With proposed index extension
 +
  for ch in reverse s index i do
 
     Writeln(i, ': ', ch);
 
     Writeln(i, ': ', ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
Note that index might return arbitrary type, not necessarily integer.
+
Note that index could be designed to return an arbitrary type (i. e. not necessarily an integer).
 
For example, in the case of tree traversal, the index might return an array of
 
For example, in the case of tree traversal, the index might return an array of
nodes from on the path from the tree root to the current node.
+
nodes describing the path from the tree root to the current node.
 
 
 
 
  
 
== Reference ==
 
== Reference ==
 
* http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html
 
* http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html

Latest revision as of 19:45, 10 April 2021

English (en) français (fr) 日本語 (ja) русский (ru)

This iterates over a collection, but where a basic for-loop uses a numerical index counter, a for-in loop instead retrieves collection elements into the counter variable for immediate use. For-in works on strings, arrays, sets, and any other custom collection that implements the required iterators. Looping over an empty collection does nothing. The counter variable can not be modified inside the loop.

The for-in loop construct is supported in Delphi from Delphi 2005 onwards. It was implemented in FPC 2.4.2.

The official documentation is here: Reference guide chapter 13


Delphi and FPC implementation

A for in loop has the following syntax:

String loop

procedure StringLoop(S: String);
var
  C: Char;
begin
  for C in S do
    DoSomething(C);
end;

Array loop

procedure ArrayLoop(A: Array of Byte);
var
  B: Byte;
begin
  for B in A do
    DoSomething(B);
end;

Set loop

type
  TColor = (cRed, cGren, cBlue);
  TColors = set of TColor;
procedure SetLoop(Colors: TColors);
var
  Color: TColor;
begin
  for Color in Colors do
    DoSomething(Color);
end;

Loop variables are temporary copies of the container value

The loop variable in a for-in loop is a copy of the value that is stored inside a container over which the for-in loop is performed.

program tempcopy;

{$IFDEF FPC}
  {$mode Delphi}
{$ENDIF}

uses SysUtils;

type
  PointerAddress = NativeUInt;

var
  pIntArr: Pointer;
  IntArr: Array of Integer;
  
procedure ArrayLoop(const AArr: Array of Integer);
var
  i: Integer;
begin
  pIntArr := @AArr;
  writeln('Argument: ', PointerAddress(pIntArr)); // <-- same memory address as Item 1
  pIntArr := @i;
  writeln('Local loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i
  for i in AArr do
  begin
    pIntArr := @i;
    writeln('Loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i
  end;
end;
  
begin
  // allow 3 items in the array
  SetLength(IntArr, 3);

  pIntArr := @pIntArr;
  writeln('global pIntArr address: ', PointerAddress(pIntArr));

  // IntArr is separate variable that points to first element
  pIntArr := @IntArr;
  writeln('global IntArr address: ', PointerAddress(pIntArr));
  pIntArr := @IntArr;
  writeln('IntArr points to: ', PointerAddress(pIntArr^));

  // addresses of the single items
  pIntArr := @IntArr[0];
  writeln('Item 1: ', PointerAddress(pIntArr)); // <-- memory address of the first item
  pIntArr := @IntArr[1];
  writeln('Item 2: ', PointerAddress(pIntArr));
  pIntArr := @IntArr[2];
  writeln('Item 3: ', PointerAddress(pIntArr));

  writeln('array loop');
  ArrayLoop(IntArr);
end.

Traversing container

To traverse a container class you need to add an enumerator for it. An Enumerator is a class structured according to the following template:

TSomeEnumerator = class
public
  function MoveNext: Boolean;
  property Current: TSomeType;
end;

There are only 2 things required for the enumerator class: a MoveNext method which asks the enumerator to step forward and a Current property which can return any appropriate type.

Thereafter you need to add the magic GetEnumerator method to the container class which returns an enumerator instance.

For example:

type
  TEnumerableTree = class;

  TTreeEnumerator = class
  private
    FTree: TEnumerableTree;
    FCurrent: TNode;
  public
    constructor Create(ATree: TEnumerableTree); 
    function MoveNext: Boolean;
    property Current: TNode read FCurrent;
  end;

  TEnumerableTree = class
  public
    function GetEnumerator: TTreeEnumerator;
  end;

constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
begin
  inherited Create;
  FTree := ATree;
  FCurrent := nil;
end;

function TTreeEnumerator.MoveNext: Boolean;
begin
  // some logic to get the next node from a tree
  if FCurrent = nil then
    FCurrent := FTree.GetFirstNode
  else
    FCurrent := FTree.GetNextNode(FCurrent);
  Result := FCurrent <> nil;
end;

function TEnumerableTree.GetEnumerator: TTreeEnumerator;
begin
  Result := TTreeEnumerator.Create(Self);
  // Note: the Result is automatically freed by the compiler after the loop.
end;

After this you are able to execute the following code:

procedure TreeLoop(ATree: TEnumerableTree);
var
  ANode: TNode;
begin
  for ANode in ATree do
    DoSomething(ANode);
end;

You will find that several basic classes (such as TList, TStrings, TCollection, TComponent ...) already have built-in enumerator support.

It is also possible to make any class enumerable if you implement the following interface in your enumerable container class:

  IEnumerable = interface(IInterface)
    function GetEnumerator: IEnumerator;
  end;

Where IEnumerator is declared as:

  IEnumerator = interface(IInterface)
    function GetCurrent: TObject;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: TObject read GetCurrent;
  end;

Multiple enumerators for one class

You can add additional enumerators to classes, objects and records.

Here is an example of adding an enumerator which traverses a TEnumerableTree in reverse order:

type
  TEnumerableTree = class;

  TTreeEnumerator = class
  ...for traversing in order, see above...
  end;

  TTreeReverseEnumerator = class
  private
    FTree: TEnumerableTree;
    FCurrent: TNode;
  public
    constructor Create(ATree: TEnumerableTree); 
    function MoveNext: Boolean;
    property Current: TNode read FCurrent;
    function GetEnumerator: TTreeReverseEnumerator; // returns itself
  end;

  TEnumerableTree = class
  public
    function GetEnumerator: TTreeEnumerator;
    function GetReverseEnumerator: TTreeReverseEnumerator;
  end;

...see above for an implementation of the TTreeEnumerator...

constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
begin
  inherited Create;
  FTree := ATree;
end;

function TTreeReverseEnumerator.MoveNext: Boolean;
begin
  // some logic to get the next node from a tree in reverse order
  if FCurrent = nil then
    FCurrent := FTree.GetLastNode
  else
    FCurrent := FTree.GetPrevNode(FCurrent);
  Result := FCurrent <> nil;
end;

function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
begin
  Result := Self;
end;

function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
begin
  Result := TTreeReverseEnumerator.Create(Self);
  // Note: the Result is freed automatically by the compiler after the loop.
end;

After this you are able to execute the following code:

procedure TreeLoop(ATree: TEnumerableTree);
var
  ANode: TNode;
begin
  for ANode in ATree.GetReverseEnumerator do
    DoSomething(ANode);
end;

FPC extensions

The following code examples illustrate constructs that are implemented only by FPC, constructs which are not supported by Delphi.

Traversing enumeration and subrange types

In Delphi, it is not possible to traverse either enumerated types or subrange types, whereas in Free Pascal we can write the following:

type
  TColor = (clRed, clBlue, clBlack);
  TRange = 'a'..'z';
var
  Color: TColor;
  ch: Char;
begin
  for Color in TColor do
    DoSomething(Color);
  for ch in TRange do
    DoSomethingOther(ch);
end.

An example of this can further be demonstrated by the following code taken from bugreport 0029147, where the type system is disabled because a hardcast is used on a value that is not necessarily part of the enum value:

type
  TSomeEnums = (One, Two, Three);

resourcestring
  SOne = 'One ape';
  STwo = 'Two apes';
  SThree = 'Three apes';

const
  SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
    SOne, STwo, SThree);

var
  i: Integer;
  SE: TSomeEnums;

begin
  for i := 0 to 4 do begin
    SE := TSomeEnums(i);  // hardcast. i can be higher than 2, but the type system is now disabled
    WriteLn(SSomeEnumStrings[SE]);
  end;
end.

The programmer instructs the compiler that i is of the enum type and the compiler will not check any further, assuming that the programmer knows best.
Of course the programmer is wrong and the compiler would have known better...
By traversing the enumerated type with for in do the hard cast is superfluous and the code becomes type safe:

type
  TSomeEnums = (One, Two, Three);

resourcestring
  SOne   = 'One ape';
  STwo   = 'Two apes';
  SThree = 'Three apes';

const
  SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
    SOne, STwo, SThree);

var
  SE: TSomeEnums;
begin
    for SE in TSomeEnums do
      WriteLn(SSomeEnumStrings[SE]);
end.

Declaring enumerators

It is also not possible in Delphi to add an enumerator without modifying the class, nor to add an enumerator to the non-class/object/record/interface type. FPC makes this possible using the new syntax operator type Enumerator. As in the following example:

type
  TMyRecord = record F1: Integer; F2: array of TMyType; end;
  TMyArrayEnumerator = class
  private
    function GetCurrent: TMyType;
  public
    constructor Create(const A: TMyRecord);
    property Current: TMyType read GetCurrent;
    function MoveNext: Boolean;
  end;

  // This is new built-in operator.
  operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
  begin
    Result := TMyArrayEnumerator.Create(A);
  end;

var
  A: MyRecord;
  V: TMyType
begin
  for V in A do
    DoSomething(V);
end.

Traversing UTF-8 strings

As a particularly useful example, the above extension allows very efficient traversal of UTF-8 strings:

uses
  LazUTF8;
interface
type
  { TUTF8StringAsStringEnumerator
    Traversing UTF8 codepoints as strings is useful when you want to know the
    exact encoding of the UTF8 character or if you like to use string
    constants in your code.
    For security reasons you should use the codepoints values (cardinals) instead.
    If speed matters, don't use enumerators. Instead use the PChar directly as 
    shown in the MoveNext method and read about UTF8. It has some interesting features. }

  TUTF8StringAsStringEnumerator = class
  private
    fCurrent: UTF8String;
    fCurrentPos, fEndPos: PChar;
    function GetCurrent: UTF8String;
  public
    constructor Create(const A: UTF8String);
    property Current: UTF8String read GetCurrent;
    function MoveNext: Boolean;
  end;

  operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;

var
  Form1: TForm1;

implementation

operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
begin
  Result := TUTF8StringAsStringEnumerator.Create(A);
end;

{ TUTF8StringAsStringEnumerator }

function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
begin
  Result:=fCurrent;
end;

constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
begin
  fCurrentPos:=PChar(A); // Note: if A='' then PChar(A) returns a pointer to a #0 string
  fEndPos:=fCurrentPos+length(A);
end;

function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
var
  l: Integer;
begin
  if fCurrentPos<fEndPos then
  begin
    l:=UTF8CharacterLength(fCurrentPos);
    SetLength(fCurrent,l);
    Move(fCurrentPos^,fCurrent[1],l);
    inc(fCurrentPos,l);
    Result:=true;
  end else
    Result:=false;
end;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  s, ch: UTF8String;
  i: SizeInt;
begin
  s:='mäßig';

  // using UTF8Length and UTF8Copy this way is slow, requiring O(n)^2
  for i:=1 to UTF8Length(s) do
    writeln('ch=',UTF8Copy(s,i,1));

  // using the above enumerator is shorter and quite fast, requiring O(n)
  for ch in s do
    writeln('ch=',ch);
end;

Using any identifiers instead of builtin MoveNext and Current

In Delphi you must use a function with the name 'MoveNext' and a property with the name 'Current' in enumerators. With FPC you can choose whatever names you wish. This is enabled by the use of the enumerator modifier, with the syntax 'enumerator MoveNext;' and 'enumerator Current;' modifiers. As in the following example:

type
  { TMyListEnumerator }

  TMyListEnumerator = object
  private
    FCurrent: Integer;
  public
    constructor Create;
    destructor Destroy;
    function StepNext: Boolean; enumerator MoveNext;
    property Value: Integer read FCurrent; enumerator Current;
  end;

  TMyList = class
  end;

{ TMyListEnumerator }

constructor TMyListEnumerator.Create;
begin
  FCurrent := 0;
end;

destructor TMyListEnumerator.Destroy;
begin
  inherited;
end;

function TMyListEnumerator.StepNext: Boolean;
begin
  inc(FCurrent);
  Result := FCurrent <= 3;
end;

operator enumerator (AList: TMyList): TMyListEnumerator;
begin
  Result.Create;
end;

var
  List: TMyList;
  i: integer;
begin
  List := TMyList.Create;
  for i in List do
    WriteLn(i);
  List.Free;
end.

Proposed extensions

Get enumerator Position if available

It is impossible to extract any information from the iterator except the current item. Sometimes other data, such as the current index, might be useful:

type
  TUTF8StringEnumerator = class
  private
    FByteIndex: Integer;
    FCharIndex: Integer;
  public
    constructor Create(const A: UTF8String);
    function Current: UTF8Char;
    function CurrentIndex: Integer;
    function MoveNext: Boolean;
  end;

  operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
  begin
    Result := TUTF8String.Create(A);
  end;

var
  s: UTF8String;
  ch: UTF8Char;
  i: Integer;
begin

  // Inefficient, as discussed above
  for i := 1 to Length(s) do
    Writeln(i, ': ', ch[i]);

  // Ok, but ugly
  i := 1;
  for ch in s do begin
    Writeln(i, ': ', ch);
    Inc(i);
  end;

  // Proposed extension
  for ch in s index i do
    Writeln(i, ': ', ch);

  // Proposed extension for traversing backwards (equivalent to downto)
  for ch in reverse s do
    Writeln(i, ': ', ch);

  // With proposed index extension
  for ch in reverse s index i do
    Writeln(i, ': ', ch);
end.

Note that index could be designed to return an arbitrary type (i. e. not necessarily an integer). For example, in the case of tree traversal, the index might return an array of nodes describing the path from the tree root to the current node.

Reference