inout and opApply

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Mon Jun 20 08:09:22 PDT 2016


On 6/20/16 9:55 AM, Dicebot wrote:
> On 06/20/2016 04:47 PM, Shachar Shemesh wrote:
>> Please consider the following program:
>>
>> struct V(T) {
>>     int opApply(scope int delegate(ref T value) dg) {
>>         return 0;
>>     }
>>     int opApply(scope int delegate(ref const T value) dg) const {
>>         return 0;
>>     }
>> }
>>
>> struct V1(T) {
>>     int opApply(scope int delegate(ref inout T value) dg) inout {
>>         return 0;
>>     }
>> }
>>
>> void main() {
>>     const V!int mytype1;
>>     V!int mytype2;
>>     V1!int mytype3;
>>
>>     foreach( v; mytype1 ) {
>>     }
>>     foreach( v; mytype2 ) {
>>     }
>>     foreach( v; mytype3 ) { // <- This line doesn't compile: cannot
>> uniquely infer foreach argument types
>>     }
>> }
>>
>> So the only way to get inout to work is... not to use it?
>
> I think Steven has mentioned this issue during his DConf inout talk.
>

Yes, it's something I'm going to post about soon.

In essence, the delegate which takes inout parameter is currently its 
own wrapping of inout. Meaning, the implicit delegate the compiler 
creates does not participate in the inout wrapping for the opApply call.

Consider how this works for the compiler. I'll give you an example:

V1!int mutableX;

foreach(ref int a; mutableX)
{
    a = 5;
}

Here, I've removed the type inference to set that issue aside for now.

What the compiler does is convert your foreach body into a local 
delegate. This local delegate looks like this:

int __foreachBody(ref int a)
{
    a = 5;

    // this is added by the compiler for flow control
    return 0;
}

and then calls:

switch(mutableX.opApply(&__foreachBody))
{
    // process specialized returns from delegate
}

So, ignoring the special opApply machinery, we can see the fundamental 
issue here is that we are passing a function pointer of type int (ref 
int a) into the opApply. However, currently, the compiler treats a 
function pointer with inout parameters to be simply a pointer to an 
inout function. The constructed delegate cannot be this, since a is ref 
int, not ref inout(int). So it cannot compile. We could change the body 
to not modify a, and possibly we could create a solution, but it's not 
ideal.

What I would like the compiler to do (and I went over this in my talk), 
is to allow the compiler to inout-wrap a delegate along with the other 
inout prameters to the function. That is, for:

int opApply(scope int delegate(ref inout T value) dg) inout

The inout inside the delegate is wrapped just like the inout of the 
'this' parameter. effectively, this becomes equivalent to several 
function signatures:

int opApply(scope int delegate(ref T value) dg)
int opApply(scope int delegate(ref const T value) dg) const
int opApply(scope int delegate(ref immutable T value) dg) immutable
int opApply(scope int delegate(ref inout T value) dg) inout

And interestingly enough, the rules are kind of backwards for delegates 
-- while inout doesn't cast to anything, delegates with inout parameters 
can cast to any type of mutability modifier (I believe this is called 
contravariance). This is because the actual function is inout, so it 
cannot harm the mutability. So something like this:

foreach(inout a; anyV1)
{
}

should work for any flavor of V1.

I think this should work with existing code, and would simply open up 
things like opApply to inout support.

The only issue is that the compiler has to assume that while calling the 
delegate, the inout parameters passed COULD change value, because it 
doesn't know whether the passed delegate is truly inout, or just matches 
the constancy of the type, which could potentially be mutable. This 
differs from current expectations of inout delegate or function pointers.

It's a lot of complex explanation, but the end result is that you could 
simply tag your opApply's with inout and have one version for all modifiers.

-Steve


More information about the Digitalmars-d mailing list