www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - question about foreach, opApply, and delegates

reply Jerry Quinn <jlquinn optonline.net> writes:
Hi, all.  I find myself a little confused about how foreach, opApply, and
delegates interact according to the docs.

Foreach on an aggregate will use the opApply call (assuming ranges aren't being
used).  So if we use a simple example below, what exactly is the delegate that
is passed to opApply?  The docs say a delegate is a pairing of an object
reference and a function, where the object is passed as the 'this' parameter to
the function.  But that doesn't seem to be the case here.

Is a virtual object with a function encompassing the body of the foreach being
silently created and passed in?

Thanks,
Jerry


class C {
  uint[] a;
  int opApply(int delegate(ref uint) dg) {
    int result = 0;
    for (size_t i=0; i < a.length; i++) {
      result = dg(a[i]);
      if (result) break;
    }
    return result;
  }
}
void foo() {
  C c = new C;
  foreach (uint v; c) { writefln(v); }
}
Jun 08 2009
next sibling parent reply grauzone <none example.net> writes:
Jerry Quinn wrote:
 Hi, all.  I find myself a little confused about how foreach, opApply, and
delegates interact according to the docs.
 
 Foreach on an aggregate will use the opApply call (assuming ranges aren't
being used).  So if we use a simple example below, what exactly is the delegate
that is passed to opApply?  The docs say a delegate is a pairing of an object
reference and a function, where the object is passed as the 'this' parameter to
the function.  But that doesn't seem to be the case here.
 
 Is a virtual object with a function encompassing the body of the foreach being
silently created and passed in?
The foreach body is like a nested function. Your example is (probably, maybe this is wrong/oversimplified) compiled to something like this: void foo() { C c = new C; int bla; void something(uint v) { bla = 123; writefln(v); } c.opApply(&something); } Note that &something is a delegate. A delegate is a pair of a context pointer and a function pointer. As you said, the context pointer can be an object reference. But in this case, it's a raw pointer into the stack. That's why you can use variables declared in the containing function (foo). I added a variable 'bla' to demonstrate this. When the nested function accesses bla, it reads the delegate's context pointer to access the stack frame of the containing function. You could say the foreach statement is a giant pile of syntactic sugar. No magic involved. When the break or continue statements are used, things get more trickier. That's why the delegate passed to opApply returns an int. I think. Also, this should go into the d.D.learn newsgroup. (Although I think it's perfectly fine to post this here, because the "gurus" don't post and probably don't even read d.D.learn.)
Jun 08 2009
parent BCS <none anon.com> writes:
Hello grauzone,

 Jerry Quinn wrote:
 
 Hi, all.  I find myself a little confused about how foreach, opApply,
 and delegates interact according to the docs.
 
 Foreach on an aggregate will use the opApply call (assuming ranges
 aren't being used).  So if we use a simple example below, what
 exactly is the delegate that is passed to opApply?  The docs say a
 delegate is a pairing of an object reference and a function, where
 the object is passed as the 'this' parameter to the function.  But
 that doesn't seem to be the case here.
 
 Is a virtual object with a function encompassing the body of the
 foreach being silently created and passed in?
 
In one way of looking at it every stack frame is a virtual struct <g> so yes, sort of.
 The foreach body is like a nested function. Your example is (probably,
 maybe this is wrong/oversimplified) compiled to something like this:
 
 void foo() {
 C c = new C;
 int bla;
 void something(uint v) {
 bla = 123;
 writefln(v);
 }
 c.opApply(&something);
 }
IIRC if you compile with the -v flag you can even see DMD running code generation for the foreach bodies.
 (Although I think
 it's perfectly fine to post this here, because the "gurus" don't post
 and probably don't even read d.D.learn.)
I may not count as a "guru" but I'm "not a beginner" and do read it.
Jun 08 2009
prev sibling next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Mon, Jun 8, 2009 at 8:55 AM, Jerry Quinn<jlquinn optonline.net> wrote:
 Hi, all. =A0I find myself a little confused about how foreach, opApply, a=
nd delegates interact according to the docs.
 Foreach on an aggregate will use the opApply call (assuming ranges aren't=
being used). =A0So if we use a simple example below, what exactly is the d= elegate that is passed to opApply? =A0The docs say a delegate is a pairing = of an object reference and a function, where the object is passed as the 't= his' parameter to the function. =A0But that doesn't seem to be the case her= e.
 Is a virtual object with a function encompassing the body of the foreach =
being silently created and passed in? Sort of. The delegate passed into the opApply in this case is actually the body of the loop. The compiler transforms the foreach loop body into a nested function. Nested functions are delegates, but their context pointer is a pointer to their enclosing function's stack frame rather than to an object. So no object is created, but it still transparently works like a delegate. This is also why in D1 returning nested functions results in undefined behavior: the pointer to the enclosing function's stack frame is no longer valid.
 Thanks,
 Jerry


 class C {
 =A0uint[] a;
 =A0int opApply(int delegate(ref uint) dg) {
 =A0 =A0int result =3D 0;
 =A0 =A0for (size_t i=3D0; i < a.length; i++) {
 =A0 =A0 =A0result =3D dg(a[i]);
 =A0 =A0 =A0if (result) break;
 =A0 =A0}
 =A0 =A0return result;
 =A0}
 }
 void foo() {
 =A0C c =3D new C;
 =A0foreach (uint v; c) { writefln(v); }
 }
Jun 08 2009
prev sibling next sibling parent downs <default_357-line yahoo.de> writes:
Jerry Quinn wrote:
 Hi, all.  I find myself a little confused about how foreach, opApply, and
delegates interact according to the docs.
 
 Foreach on an aggregate will use the opApply call (assuming ranges aren't
being used).  So if we use a simple example below, what exactly is the delegate
that is passed to opApply? 
 The docs say a delegate is a pairing of an object reference and a function,
where the object is passed as the 'this' parameter to the function.  But that
doesn't seem to be the case here.
If the docs say that, they're wrong. Generally speaking, a delegate is a pairing of a function pointer and a context, where the context can be a struct pointer, a class reference *or a stackframe*, as is the case with opApply. Hope that clears things up.
Jun 08 2009
prev sibling parent Jerry Quinn <jlquinn optonline.net> writes:
downs Wrote:

 Jerry Quinn wrote:
 Hi, all.  I find myself a little confused about how foreach, opApply, and
delegates interact according to the docs.
 
 Foreach on an aggregate will use the opApply call (assuming ranges aren't
being used).  So if we use a simple example below, what exactly is the delegate
that is passed to opApply? 
 The docs say a delegate is a pairing of an object reference and a function,
where the object is passed as the 'this' parameter to the function.  But that
doesn't seem to be the case here.
If the docs say that, they're wrong. Generally speaking, a delegate is a pairing of a function pointer and a context, where the context can be a struct pointer, a class reference *or a stackframe*, as is the case with opApply. Hope that clears things up.
Yes, that helps. All 3 replies are basically the same, and the docs are clearly insufficient to describe what's actually happening in a delegate. I'll file a bug. Thanks!
Jun 08 2009