DIP 1011--extern(delegate)--Preliminary Review Round 1

Jonathan Marler via Digitalmars-d digitalmars-d at puremagic.com
Fri Jul 14 10:18:50 PDT 2017


On Friday, 14 July 2017 at 12:52:56 UTC, Steven Schveighoffer 
wrote:
> On 7/14/17 6:43 AM, Mike Parker wrote:
>> DIP 1011 is titled "extern(delegate)".
>> 
>> https://github.com/dlang/DIPs/blob/master/DIPs/DIP1011.md
>> 
>> All review-related feedback on and discussion of the DIP 
>> should occur in this thread. The review period will end at 
>> 11:59 PM ET on July 28 (3:59 AM GMT July 29), or when I make a 
>> post declaring it complete.
>> 
>> At the end of Round 1, if further review is deemed necessary, 
>> the DIP will be scheduled for another round. Otherwise, it 
>> will be queued for the formal review and evaluation by the 
>> language authors.
>> 
>> Thanks in advance to all who participate.
>> 
>> Destroy!
>
> It seems reasonable.
>
> One concern I have is this:
>
> extern(delegate) ref int foo(ref int x) { return x; }
>
> int x;
>
> auto dg = &x.foo; // is dg an int * or a delegate?
>
> Currently, this means the former. I'm concerned about 
> ambiguities and how to resolve them if all of a sudden someone 
> decides it's a good idea to change their functions to 
> extern(delegate), and code like this silently switches over. Or 
> someone tries to form a delegate from one of these, but instead 
> ends up calling the function instead.
>
> -Steve

I thought about this use case and it's an interesting one.

TLDR; it should return "int delegate()" not "int*"

First I want to say this use case happens less often then you 
might think. It's only a problem when the function has no 
arguments. If the function does have arguments then omitting the 
parameter list clearly means the application wants a 
delegate/function pointer. Currently, taking the address of a 
member function call will return a delegate, even if it returns a 
reference.

import std.stdio;
struct Foo
{
     static int staticInt;
     ref int bar()
     {
         return staticInt;
     }
}

Foo foo;
writeln(typeid(&foo.bar)); // prints "int delegate()"

// NOTE: If you want to take the address of the return value, you 
add the () to the call, i.e.
writeln(typeid(&foo.bar())); // prints "int*"

Furthermore, since taking the address of a UFCS call currently 
has no meaning, this would evaluate to int* if it was a UFCS call:

ref int baz(ref Foo foo)
{
     static int staticInt;
     return staticInt;
}
writeln(typeid(&foo.baz)); // prints "int*"

So the semantic analyzer has a precendence for the '&' operator 
when applied to "dotted" function calls.  It checks if the 
expression could result in a delegate, if not then it falls back 
to taking the address of the return value.

As you have noticed, since extern(delegate) adds meaning to 
taking the address of a UFCS call, the semantic analyzer will now 
be able to create a delegate in a place where it previously 
couldn't.

extern(delegate) ref int baz(ref Foo foo)
{
     static int staticInt;
     return staticInt;
}
writeln(typeid(&foo.baz)); // is it "int*" or "int delegate()"?

The implementation could go either way but I think it should be 
consistent with the semantics for delegates meaning it should 
result in "int delegate()" and here's why:

   * Having an inconsistent precedence for member functions and 
extern(delegate) functions would be confusing

   * If taking the address of the return value has precendence, we 
would have to come up with a new syntax when the application 
wants to do the opposite.  Currently, if the application really 
means they want to take the address of the return value, they can 
simply add an empty parameter list to disambiguate the meaning, 
i.e. they could use &foo.bar() instead of &foo.bar.  If we used 
the opposite precedence, there is currently no way to say we 
really want a delegate instead of taking the address of the 
return value.

   * If an existing project does start using extern(delegate), in 
almost all cases the change from taking the address of the return 
value to getting a delegate will almost always result in a 
compile time error.  In your example if the meaning of &x.foo 
changed, it would return a different type which would very likely 
be caught by the compiler, depending of course on how the value 
is used.  There are cases where it wouldn't be caught, but it's 
very unlikely.

   * This only affects code that goes back and modifies their 
application to use extern(delegate).  In other words, it's not 
breaking existing code unless that existing code itself changes 
to use this feature.


More information about the Digitalmars-d mailing list