www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How can I put the current value of a variable into a delegate?

reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
Delegates can be a pain, as they often have results different 
from what one would intuitively expect. This can easily result in 
bugs.

Here's a line that caused a bug that took me awhile to find:
```
foreach(card; unitCards) card.submitted = delegate() => 
selectUnit(card.unit);
```

Each `UnitInfoCard` object (which `card` is a member of) contains 
a `Unit` object called `unit`. The intention of this line was 
that each object in `unitCards` would call `selectUnit` with it's 
own `unit` every time it calls `submitted`. Instead, every card 
calls `submitted` with the *last* value of `card`.

This is because the delegate assignment causes the local `card` 
variable to remain alive. The delegate that's assigned is linked 
to this variable itself, not the value at the time that the 
delegate is assigned.

Is there a way I can dereference a variable when placing it in a 
delegate, so that it's current value is used, rather than the 
variable itself?
May 05
next sibling parent Nick Treleaven <nick geany.org> writes:
On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:
 This is because the delegate assignment causes the local `card` 
 variable to remain alive. The delegate that's assigned is 
 linked to this variable itself, not the value at the time that 
 the delegate is assigned.
This is https://issues.dlang.org/show_bug.cgi?id=23136. Perhaps it can be fixed in the next edition.
 Is there a way I can dereference a variable when placing it in 
 a delegate, so that it's current value is used, rather than the 
 variable itself?
I think you would need to make an array before the loop, assign to an indexed element and use that in the delegate.
May 06
prev sibling next sibling parent Nick Treleaven <nick geany.org> writes:
On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:
 Here's a line that caused a bug that took me awhile to find:
 ```
 foreach(card; unitCards) card.submitted = delegate() => 
 selectUnit(card.unit);
 ```
I think you can do: ```d import std.algorithm.iteration : each; unitCards.each!(c => c.submitted = () => selectUnit(c.unit)); ```
May 06
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:
 Delegates can be a pain, as they often have results different 
 from what one would intuitively expect. This can easily result 
 in bugs.

 Here's a line that caused a bug that took me awhile to find:
 ```
 foreach(card; unitCards) card.submitted = delegate() => 
 selectUnit(card.unit);
 ```

 Each `UnitInfoCard` object (which `card` is a member of) 
 contains a `Unit` object called `unit`. The intention of this 
 line was that each object in `unitCards` would call 
 `selectUnit` with it's own `unit` every time it calls 
 `submitted`. Instead, every card calls `submitted` with the 
 *last* value of `card`.
Yes, this is because the foreach loop reuses the same memory slot for `card`. Even though this is allocated as a closure, it still only allocates the frame stack of the *enclosing function*, and does not allocate a new slot for each loop iteration. You can force this by using a lambda which allocates the closure: ```d foreach(card; unitCards) card.submitted = (c2) { return () => selectUnit(c2.unit); }(card); ``` This is a lambda which accepts `card` as a parameter, and returns an appropriate delegate. It is important to use a parameter, because if you just use card inside there, it's still using the single stack frame of the calling function! I renamed the inner parameter `c2` to avoid confusion, but you could name it `card` also. Essentially, the stack frame of the inner function is now allocated a closure, and it has it's own reference to `card` as a parameter. This is a very old issue: https://issues.dlang.org/show_bug.cgi?id=2043 since "moved" to https://issues.dlang.org/show_bug.cgi?id=23136 I would love to see a solution, but the workaround at least exists! -Steve
May 06
next sibling parent reply Rene Zwanenburg <renezwanenburg gmail.com> writes:
On Monday, 6 May 2024 at 16:41:38 UTC, Steven Schveighoffer wrote:
 This is a very old issue: 
 https://issues.dlang.org/show_bug.cgi?id=2043 since "moved" to 
 https://issues.dlang.org/show_bug.cgi?id=23136

 I would love to see a solution, but the workaround at least 
 exists!

 -Steve
most people expect. Since it's an unsolved problem to keep links working for 10+ years I gave up looking for something official about the subject. Here's an SO question about it though: https://stackoverflow.com/questions/14184515/action-delegate-uses-the-last-values-of-variables-declared-outside-foreach-loop
May 08
parent Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Wednesday, 8 May 2024 at 12:29:05 UTC, Rene Zwanenburg wrote:


 most people expect.
Wow! I wonder if D would be willing to allow such a breaking change with the release of Phobos 3. My choice would be to have it use the current value by default for value types, but allow them to be linked to the same memory address using `*&` when the variable is placed in a delegate. I think that the distinction between value types and reference types should be consistent. If such a breaking change isn't considered acceptable, I suppose a new operator can be introduced for dereferencing a variable conflict with any existing use of those symbols.
May 08
prev sibling parent Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Monday, 6 May 2024 at 16:41:38 UTC, Steven Schveighoffer wrote:
 On Monday, 6 May 2024 at 06:29:49 UTC, Liam McGillivray wrote:
 Delegates can be a pain, as they often have results different 
 from what one would intuitively expect. This can easily result 
 in bugs.

 Here's a line that caused a bug that took me awhile to find:
 ```
 foreach(card; unitCards) card.submitted = delegate() => 
 selectUnit(card.unit);
 ```

 Each `UnitInfoCard` object (which `card` is a member of) 
 contains a `Unit` object called `unit`. The intention of this 
 line was that each object in `unitCards` would call 
 `selectUnit` with it's own `unit` every time it calls 
 `submitted`. Instead, every card calls `submitted` with the 
 *last* value of `card`.
Yes, this is because the foreach loop reuses the same memory slot for `card`. Even though this is allocated as a closure, it still only allocates the frame stack of the *enclosing function*, and does not allocate a new slot for each loop iteration. You can force this by using a lambda which allocates the closure: ```d foreach(card; unitCards) card.submitted = (c2) { return () => selectUnit(c2.unit); }(card); ``` This is a lambda which accepts `card` as a parameter, and returns an appropriate delegate. It is important to use a parameter, because if you just use card inside there, it's still using the single stack frame of the calling function! ... I would love to see a solution, but the workaround at least exists! -Steve
Well that's something. It's not a very good solution for a language that aims for readability. It took me awhile looking at it to figure out what it is about, as I'm not familiar with this syntax. The solution that I did before seeing this was to add a function to `UnitInfoCard` to give it a delegate with a `Unit unit` parameter, and then that function would give that function with the `unit` parameter set to itself to it's own `submitted` member. I will probably keep it like this for readability. ``` void clickAction(void delegate(Unit) safe clickAction) { submitted = () => clickAction(unit); } ```
May 08