13

I can do the following in C++

for_each(vec->begin(), vec->end(), [](int n){cout << n << " " << endl;});

I would like to do the same/similar in Delphi. I asked a question earlier to C++ developers, and wanted to produce similar example in OOP Pascal. The lambda (anonymous method) part of the question is not relevant really, but I was wondering If I could do the same in Delphi XE2.

I'm asking because I have XE, and I'm not sure whether it has been added. Thanks.

Community
  • 1
  • 1
Mihaela
  • 2,482
  • 3
  • 21
  • 27

2 Answers2

11

Probably the closest Delphi analogue to std::vector<T> is TList<T>. You can iterate over the list with a for in loop:

var
  Item: Integer;
  List: TList<Integer>;
....
for Item in List do
  Writeln(Item);

If you have a dynamic array rather than a TList<T> then you can use for in to iterate over the elements. In fact, all the built in containers support for in and it is easy to add support for for in to your own classes.

In C++ there is nothing like a for in loop and so the idiom is to use an STL algorithm. That's what drives you to using an anonymous function. In Delphi with the for in syntax you can express "iterate over all members of the container" in a natural way without resorting to anonymous methods.

Generics were added to Delphi in Delphi 2009 and the for in loop was added in Delphi 2005, so all this is available to you in XE. For what it's worth, anonymous were also added in Delphi 2009.

What you must realise is that Delphi generics are less powerful than C++ templates. Although you talk about a generic foreach, your code is not generic in the sense that it has specialised to int. You could write a generic version of your code in C++ but this would be much harder to do with Delphi generics due to the inherent limitations of generics when compared to templates. An attempt to write the above code in a generic way in Delphi would founder at the point where you tried to call Writeln. Whilst that would be trivial with C++ templates it is frustratingly out of reach for generics.

Update: In the comments you ask if there is a slick way to add the contents of one container to another. The AddRange method does that. TList<T>.AddRange() has three overloaded variants that receive one of the following input parameters: array of T, Collection: IEnumerable<T> or Collection: TEnumerable<T>. All the standard generic containers follow a similar pattern.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • That's how I did it: for i := 0 to n - 1 do result.Add(i); But I was wondering whether such generic function existed :) – Mihaela Mar 09 '12 at 18:53
  • 3
    Lambda/Closure/Anonfunction syntax is so CLUNKY in delphi that it's pretty great that you don't need it for `for` loops. C++ idioms as usual, don't translate well to pascal. But C# and Delphi are pretty close indeed. – Warren P Mar 09 '12 at 19:15
  • I find it quite useful in C++. Not only is the code cleaner(for small anonymous functions), but in C++ it often gets inlined and generates faster code. – Mihaela Mar 09 '12 at 19:17
  • @WarrenP Yes, the syntax for anon procs is a problem – David Heffernan Mar 09 '12 at 19:23
  • Sorry I pasted the wrong code :(. I did the iteration on Generics.Collections.TList: for aInt in aGen do Write(Format('%d ', [aInt])); – Mihaela Mar 09 '12 at 20:04
0

This is an excerpt from my Delphi generics-based unit Zoomicon.Collections:

uses System.Rtti; //for RttiContext

//...

type

  TListEx<T> = class(TList<T>)
    //...
    {ForEach}
    class procedure ForEach(const Enum: TEnumerable<T>; const Proc: TProc<T>; const Predicate: TPredicate<T> = nil); overload;
    procedure ForEach(const Proc: TProc<T>; const Predicate: TPredicate<T> = nil); overload;
  end;

//...

{$region 'ForEach'}

class procedure TListEx<T>.ForEach(const Enum: TEnumerable<T>; const Proc: TProc<T>; const Predicate: TPredicate<T> = nil);
begin
  if Assigned(Proc) then
    for var item in Enum do
      if (not Assigned(Predicate)) or Predicate(item) then
        Proc(item);
end;

procedure TListEx<T>.ForEach(const Proc: TProc<T>; const Predicate: TPredicate<T> = nil);
begin
  {TListEx<T>.}ForEach(self, Proc, Predicate);
end;

{$endregion}

try using with anonymous methods so that you can capture context and pass to the TProc that you pass as a reference (since that only accepts T). See below how to pass DX, DY for example:

type
  Manipulator = class(TFrame)
  pubic
    class procedure MoveControls(const Controls: TControlList; const DX, DY: Single); overload;
    procedure MoveControls(const DX, DY: Single); overload;
  end;

class procedure TManipulator.MoveControls(const Controls: TControlList; const DX, DY: Single);
begin
  if (DX <> 0) or (DY <> 0) then
    TListEx<TControl>.ForEach(Controls,
      procedure (Control: TControl)
      begin
        with Control.Position do
          Point := Point + TPointF.Create(DX, DY);
      end
    );
end;

procedure TManipulator.MoveControls(const DX, DY: Single);
begin
  if (DX <> 0) or (DY <> 0) then
    begin
    BeginUpdate;
    MoveControls(Controls, DX, DY);
    EndUpdate;
    end;
end;

myManipulator.MoveControls(20, 20);

You can find more advanced versions though there that can also cast items from collections to the class you need:

TObjectListEx<TControl>.ForEachClass<TButton>(Controls, SomeProc);

which is an optimization (since it doesn't construct an intermediate list) compared to doing something like:

var list := TObjectListEx<TControl>.GetAllClass<TButton>(Controls);
list.ForEach(SomeProc);
FreeAndNil(list);

Currently at the repository of an App I'm developing: https://github.com/Zoomicon/READCOM_App/tree/master/Zoomicon.Generics/Collections (will probably move to its own repository in the future)

George Birbilis
  • 2,782
  • 2
  • 33
  • 35