inout and function/delegate parameters

Steven Schveighoffer schveiguy at yahoo.com
Mon Mar 5 05:49:46 PST 2012


On Sat, 25 Feb 2012 09:02:47 -0500, Timon Gehr <timon.gehr at gmx.ch> wrote:

> On 02/24/2012 05:26 PM, Steven Schveighoffer wrote:
>> On Sun, 19 Feb 2012 09:27:42 -0500, Stewart Gordon <smjg_1998 at yahoo.com>
>> wrote:
>>
>>> At the moment, if a function has an inout parameter, it must have an
>>> inout return type.
>>>
>>> But this prevents doing stuff like
>>>
>>> void test(ref inout(int)[] x, inout(int)[] y) {
>>> x = y;
>>> }
>>
>> This is a legitimate request, I think there is an effort underway by
>> myself Timon and Kenji to make this work. (well mostly Kenji and Timon)
>>
>>> or passing the constancy through to a delegate instead of a return  
>>> value.
>>>
>>> A typical use case of the latter is to define an opApply that works
>>> regardless of the constancy of this and allows the delegate to modify
>>> the iterated-through objects _if_ this is mutable.
>>>
>>> int opApply(int delegate(ref inout(T)) dg) inout;
>>
>> What you ask isn't possible given the current design of inout. During
>> inout function execution, inout is a special form of const, even if the
>> object on which opApply is being called is mutable.
>>
>
> The call site has enough information to type check the call given that  
> there is some syntax to tie the two inout qualifiers together. inout  
> shouldn't imply const, we already have const for that.

inout is defined as "during this function execution the given parameter  
will be treated as const".  This is necessary to have a single  
implementation for all flavors of const.

Remember, inside the function the compiler has no idea that the actual  
data is mutable, const or immutable.  One of the cornerstones of inout is  
that it must generate one function.

>>> But then I realised a potential ambiguity:
>>> (a) the constancy is passed through to the delegate
>>> (b) the delegate has an inout parameter in its own right
>>>
>>> If we go by interpretation (b), then each signature contains only one
>>> inout, so even if we relaxed the rules to allow this it would just be
>>> equivalent to
>>>
>>> int opApply(int delegate(ref const(T)) dg) const;
>>
>> Yes, this is what I think it should be equivalent to. As I said, inside
>> opApply, inout is like const, and is transitive. So you cannot
>> "temporarily" make it mutable.
>>
>
> 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.

It would be ideal for inout to solve this, but it's not chartered in such  
a way to do so.  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.

>>> however, this won't always be true in the general case.
>>>
>>> The essence of functions with inout parameters is that they have a
>>> hidden constancy parameter. This is essentially a template parameter,
>>> except that only one instance of the function is generated, rather
>>> like Java generics. If we made this parameter explicit in the code, we
>>> could distinguish the two meanings:
>>
>> No the constancy is not a parameter (not even a hidden one). The magic
>> of inout happens at the call, not inside the function.
>
> The same is (mostly) true for Java generics.
>
>> Inside, it's just another type of const.
>>
>> However, there is an entire part of delegates that is yet untapped --
>> implicit conversion of delegates. For example, int delegate(ref const
>> int x) could be implicitly converted to int delegate(ref int x). Maybe
>> there is something there that can be used to solve this problem. Maybe
>> there is a rule we could apply when calling an inout-enabled function
>> that allows implicit conversion of a delegate to an inout -flavored
>> version of the delegate (as long as the normal delegate matches the
>> decided inout constancy factor). But that violates transitivity. I'm not
>> sure Walter would go for this.
>>
>
> Unfortunately this violates type safety. Also see:
> http://d.puremagic.com/issues/show_bug.cgi?id=7542

That bug does not exactly capture what I said.  The inout translation  
*must* happen at the same point where inout is resolved at the call site.

For example, you can't do this:

int delegate(inout(int)*) iod;
int delegate(int *) md;
iod = md; // error, we don't know what inout will resolve to.

But this could be made to work:

void foo(inout(int)* x, int delegate(inout(int)*) d) { d(x);}

int x;
foo(&x, md); // translation is valid, because we know what inout resolves  
to.
foo(&x, iod); // also valid, because iod must have been defined to  
actually take an inout parameter.

Again, if you look at inout as a form of const, this breaks transitivity,  
it's kind of a cousin of logical const.  Walter needs to be involved to  
have his say.  I think it could be made to work.

The more I think about it, the more I think this is worth changing.  inout  
provides a unique way to make something that is normally a template that  
generates multiple copies of the same code into one that generates one  
copy.  The large benefit of it is you can write one function instead of 3,  
and still have it not be a template.

-Steve


More information about the Digitalmars-d mailing list