Thread overview
templated overload of opAssign
Apr 03, 2021
kdevel
Apr 04, 2021
frame
Apr 04, 2021
tsbockman
Apr 05, 2021
frame
Apr 05, 2021
tsbockman
Apr 05, 2021
kdevel
Apr 05, 2021
Imperatorn
Apr 05, 2021
tsbockman
Apr 05, 2021
kdevel
Apr 05, 2021
Paul Backus
April 03, 2021

Why does this code

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?

April 04, 2021

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.

April 04, 2021

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:

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;
}
April 05, 2021

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?

April 05, 2021

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;
}
April 05, 2021

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.

April 05, 2021

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:

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

April 05, 2021

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.)

April 05, 2021

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!

April 05, 2021

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?

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.