inout and function/delegate parameters

Steven Schveighoffer schveiguy at yahoo.com
Mon Mar 5 15:27:32 PST 2012


On Mon, 05 Mar 2012 18:01:34 -0500, Timon Gehr <timon.gehr at gmx.ch> wrote:

> On 03/05/2012 11:31 PM, Steven Schveighoffer wrote:
>> On Mon, 05 Mar 2012 11:17:41 -0500, Stewart Gordon <smjg_1998 at yahoo.com>
>> wrote:
>>
>>> On 05/03/2012 13:49, Steven Schveighoffer wrote:
>>>> On Sat, 25 Feb 2012 09:02:47 -0500, Timon Gehr <timon.gehr at gmx.ch>
>>>> wrote:
>>> <snip>
>>>>> inout means 'some qualifier but we don't know which one'. The call
>>>>> site on the other
>>>>> hand knows which qualifier it is. If the call is made to work there
>>>>> is no 'making it
>>>>> mutable', because it is mutable all the time. The inout function
>>>>> cannot change the data
>>>>> because it cannot know what the constancy is.
>>>>
>>>> What you would propose is that a delegate which takes a
>>>> const/mutable/immutable implicitly
>>>> translates to one which takes an inout. I agree it can be made to
>>>> work, but it does not
>>>> fit into the current definition of inout.
>>>
>>> I'm not sure if you understand correctly. Is isn't a matter of
>>> implicit conversion of delegates from one type to another, but a
>>> matter of (in this case) opApply accepting a delegate with a parameter
>>> whose constancy matches that of this.
>>
>> I understand the problem and the proposed solution quite well.
>>
>>>> It would be ideal for inout to solve this, but it's not chartered in
>>>> such a way to do so.
>>>
>>> If I'm not mistaken, inout isn't chartered to do anything particular
>>> when it occurs in a function signature nested within another. The spec
>>> just leaves it ambiguous.
>>
>> No, inout is not modifiable during the function execution. Given the
>> transitivity of const and immutable, inout itself must also be  
>> transitive.
>>
>> Think about it this way:
>>
>> inout(int)* foo(inout(int)* x)
>> {
>> // begin here, at this point x is not modifiable
>> // *x = 5; // not allowed!
>> return x;
>> // end here, at this point inout reverts back to it's original constancy
>> (including return value).
>> }
>
> I don't think this is the best way to think about inout. Try to think  
> about it as if the constancy was determined, but unknown. It does not  
> actually change, the function just does not know what it is because it  
> has to work in a polymorphic way.

Best way or not, it was designed and accepted using these assumptions.   
Again, I am not against the idea, I just want to hear what Walter and  
company think about it.

>
>>
>> The original definition I used for inout was "scoped const", meaning
>> it's const within the scope and reverts back after the scope. I did not
>> plan for having the constancy "temporarily" revert back to the original
>> constancy. What you wish for is this to take a delegate which matches
>> the constancy of the x parameter:
>>
>> inout(int)* foo(inout(int) *x, void delegate(inout(int)* p) dg)
>> {
>> dg(x); // valid, since the type matches
>> return x; // oops, now x could have changed! Even through it's an inout
>> reference!
>> }
>>
>
> Well, that can happen even for const references. What is your point?

Not via the const parameter.  For example, mark the delegate and the  
function as pure, it cannot happen with const.

>>>> It's currently transitive, and this would break transitivity. If we
>>>> want to look at
>>>> fundamentally redefining inout so that it can break transitivity,
>>>> then we can look at
>>>> that. But I don't think this is a simple "add-on" to the current
>>>> functionality.
>>> <snip>
>>>
>>> Can you give an example of how it breaks transitivity?
>>
>> Using the above foo function:
>>
>> void main()
>> {
>> void bar(int *x) {*x = 5;}
>> int i = 2;
>> foo(&i, &bar); // this sets i to 5 via the inout reference we pass into  
>> it.
>> }
>>
>> In other words, inout is transitively not constant during the function
>> call.
>>
>
> Because it has been matched as *mutable*. There is no issue.

There are two parts to inout, one is that it can be one function called 3  
different ways, the other is that you know it's constant during function  
execution.  Some people like that second part, even if it doesn't fully  
guarantee everything.  I.e. there's a reason people use const in C++  
besides it being trendy.

>
>> I'm not saying we cannot bend the rules to allow for this, because
>> clearly it doesn't violate the "true" constancy type of the data passed
>> in, but I don't think it's a straightforward change conceptually. I'm
>> not a compiler writer, so I don't know how this works in their minds,
>> I'd like to have their input.
>
> Implementation of what you propose should be quite simple. The issue is  
> with the design, i.e. allowing both tying the inout in the delegate  
> parameter signature to the inout in the enclosing signature and having  
> an independent inout delegate parameter needs workable syntax.

I think it can be done:

1. determine inout match based on existing rules, excluding delegate  
parameters.
2. change delegate parameter inouts to matched value.
3. Use implicit delegate conversion (using contravariance) to allow  
passing delegates of proper type.

For example:

void foo(inout(int)* x, void delegate(inout(int)* y) dg);

void main()
{
    int mx;
    immutable int ix;
    const int cx;

    void bar1(int *mp) {}
    void bar2(immutable(int) *ip) {}
    void bar3(const(int) *cp) {}
    void bar4(inout(int) *iop) {}

    // inout matched as mutable due to mx, signature becomes void foo(int  
*x, void delegate(int *y) dg);
    foo(&mx, &bar1); // fine, direct match of both parameters
    foo(&mx, &bar2); // not fine, immutable delegate does not implicitly  
convert to mutable
    foo(&mx, &bar3); // fine, const delegate can implicitly convert to  
mutable
    foo(&mx, &bar4); // fine, inout delegate can implicitly convert to  
mutable

    // signature becomes void foo(immutable(int) *x, void  
delegate(immutable(int) *y) dg);
    foo(&ix, &bar1); // error
    foo(&ix, &bar2); // ok
    foo(&ix, &bar3); // fine, const delegate can implicitly convert to  
immutable
    foo(&ix, &bar4); // fine, inout delegate can implicitly convert to  
immutable

    // signature becomes void foo(const(int) *x, void delegate(const(int)  
*y) dg);
    foo(&cx, &bar1); // error
    foo(&cx, &bar2); // error
    foo(&cx, &bar3); // ok
    foo(&cx, &bar4); // ok

    // etc...
}

Note that Walter has explicitly rejected contravariance conversion for  
delegates.  You have good persuasive skills, maybe you can help :)  See  
bug http://d.puremagic.com/issues/show_bug.cgi?id=3180 and  
http://d.puremagic.com/issues/show_bug.cgi?id=3075

-Steve


More information about the Digitalmars-d mailing list