www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - templated overload of opAssign

reply kdevel <kdevel vogtner.de> writes:
Why does this code

```d
import std.stdio,std.typecons;

struct EC {
    Exception [] ex;
    auto opAssign (X: void) (lazy X f)
    {
       writeln (__PRETTY_FUNCTION__);
       try return f (); catch (Exception e) ex ~= e;
    }
}

class E : Exception { this (string s) { super (s); } }
void bar (int i) { if (i == 1) throw new E ("E"); }

void main ()
{
    EC ec;

    ec.opAssign (bar (1)); // okay
//   ec = bar (1); // Error: expression bar(1) is void and has no 
value

    ec.writeln;
}
```

compile with the abovementioned error?
Apr 03 2021
next sibling parent reply frame <frame86 live.com> writes:
On Saturday, 3 April 2021 at 13:46:17 UTC, kdevel wrote:
 Why does this code
    ec.opAssign (bar (1)); // okay
 //   ec = bar (1); // Error: expression bar(1) is void and has 
 no value
 compile with the abovementioned error?
You cannot assign void returned from bar() as parameter to opAssign(). The lazy keyword creates some internal delegate, thus opAssign() works instead. I'm afraid lazy storage class only works with arguments, not rvalues.
Apr 04 2021
parent reply tsbockman <thomas.bockman gmail.com> writes:
On Sunday, 4 April 2021 at 16:38:10 UTC, frame wrote:
 On Saturday, 3 April 2021 at 13:46:17 UTC, kdevel wrote:
 Why does this code
    ec.opAssign (bar (1)); // okay
 //   ec = bar (1); // Error: expression bar(1) is void and has 
 no value
 compile with the abovementioned error?
You cannot assign void returned from bar() as parameter to opAssign(). The lazy keyword creates some internal delegate, thus opAssign() works instead.
Thus, the solution is to use an explicit `delegate` instead of `lazy`: ```D import std.stdio,std.typecons; struct EC { Exception [] ex; auto opAssign (X: void delegate()) (scope X f) { writeln (__PRETTY_FUNCTION__); try return f (); catch (Exception e) ex ~= e; } } class E : Exception { this (string s) { super (s); } } auto bar (int i) { return () { if (i == 1) throw new E ("E"); }; } void main () { EC ec; ec = bar (1); // okay ec.writeln; } ```
Apr 04 2021
next sibling parent reply frame <frame86 live.com> writes:
On Sunday, 4 April 2021 at 18:05:04 UTC, tsbockman wrote:

 Thus, the solution is to use an explicit `delegate` instead of 
 `lazy`:
Yes, I forgot to mention that. Could you please explain why you set 'scope' here? Isn't it wanted to keep references here?
Apr 04 2021
parent tsbockman <thomas.bockman gmail.com> writes:
On Monday, 5 April 2021 at 05:22:22 UTC, frame wrote:
 On Sunday, 4 April 2021 at 18:05:04 UTC, tsbockman wrote:

 Thus, the solution is to use an explicit `delegate` instead of 
 `lazy`:
Yes, I forgot to mention that. Could you please explain why you set 'scope' here? Isn't it wanted to keep references here?
`scope` here indicates that no references to the `f` delegate itself will be escaped from `opAssign`, giving the caller the option of allocating the closure on the stack. `opAssign` may retain a reference to an `Exception` thrown by the delegate, but that's OK because the `Exception` is not part of the `f` delegate data structure, not even indirectly or transitively. (I can explain/justify this in more detail if that still doesn't make sense.)
Apr 05 2021
prev sibling parent reply kdevel <kdevel vogtner.de> writes:
On Sunday, 4 April 2021 at 18:05:04 UTC, tsbockman wrote:
```
[...]
 You cannot assign void returned from bar() as parameter to 
 opAssign(). The lazy keyword creates some internal delegate, 
 thus opAssign() works instead.
[...]
 auto bar (int i) {
     return () {
     	if (i == 1)
             throw new E ("E");
     };
 }
``` You changed the definition of ``bar`` while the exception collector (``EC``) is meant to catch and collect an exception thrown from the *unmodified* function. It seems that the operator ``+=`` or ``~=`` may be better suited to express that intent. Rewriting this in terms of ``opOpAssign`` works as expected: ``` import std.stdio; struct EC { Exception [] ex; auto opOpAssign (string op: "+", X: void) (lazy X f) { writeln (__PRETTY_FUNCTION__); try return f (); catch (Exception e) ex ~= e; } auto opOpAssign (string op: "~", X: void) (lazy X f) { writeln (__PRETTY_FUNCTION__); try return f (); catch (Exception e) ex ~= e; } } class E : Exception { this (string s) { super (s); } } void bar (int i) { if (i == 1) throw new E ("E"); } void main () { EC ec; ec.opOpAssign!"+" (bar (1)); // okay ec += bar (1); // okay ec ~= bar (1); // okay ec.writeln; } ```
Apr 05 2021
next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Monday, 5 April 2021 at 15:05:24 UTC, kdevel wrote:
 On Sunday, 4 April 2021 at 18:05:04 UTC, tsbockman wrote:
 ```
 [...]
 [...]
[...]
 [...]
``` [...]
Nice, is this documented somewhere? 🤔 Maybe we could add a better error message or smth.
Apr 05 2021
prev sibling parent reply tsbockman <thomas.bockman gmail.com> writes:
On Monday, 5 April 2021 at 15:05:24 UTC, kdevel wrote:
 You changed the definition of ``bar`` while the exception 
 collector (``EC``) is meant to catch and collect an exception 
 thrown from the *unmodified* function.
My point was that the code will work if you do explicitly what `lazy` does implicitly. If you don't want to modify `bar`, then it looks like this: ```D import std.stdio,std.typecons; struct EC { Exception [] ex; auto opAssign (X) (scope X f) if(is(X : void delegate()) || is(X : void function())) { writeln (__PRETTY_FUNCTION__); try return f (); catch (Exception e) ex ~= e; } } class E : Exception { this (string s) { super (s); } } void bar (int i) { if (i == 1) throw new E ("E"); } void main () { EC ec; ec = () { bar (1); }; // okay ec.writeln; } ```
 It seems that the operator ``+=`` or ``~=`` may be better
 suited to express that intent. Rewriting this in terms of
 ``opOpAssign`` works as expected:
Given that you define the operation to append `~` to `ex` rather than overwriting it, `~=` is the best fit, I think. However, `=` and `~=` should not treat `lazy void` parameters differently. They should either both work, or neither. I checked and this is actually a very old regression; both worked way back in DMD 2.061. So, I've filed a front-end bug report: https://issues.dlang.org/show_bug.cgi?id=21802
Apr 05 2021
parent kdevel <kdevel vogtner.de> writes:
On Monday, 5 April 2021 at 20:59:34 UTC, tsbockman wrote:
 However, `=` and `~=` should not treat `lazy void` parameters 
 differently. They should either both work, or neither. I 
 checked and this is actually a very old regression; both worked 
 way back in DMD 2.061. So, I've filed a front-end bug report:
     https://issues.dlang.org/show_bug.cgi?id=21802
Thanks!
Apr 05 2021
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Saturday, 3 April 2021 at 13:46:17 UTC, kdevel wrote:
 Why does this code

 [...]
 ```d
    ec.opAssign (bar (1)); // okay
 //   ec = bar (1); // Error: expression bar(1) is void and has 
 no value
 ```
 [...]

 compile with the abovementioned error?
This is a compiler bug. You're not allowed to have a `void` expression on the right-hand side of an assignment, but you are allowed to pass a `void` expression to a function that takes a `lazy` parameter. Currently, the compiler checks for errors before rewriting the assignment to an `opAssign` call, which means that it will issue an error even in cases where the rewrite would have worked. What it should do instead is rewrite the assignment first, and *then* check for errors.
Apr 05 2021