Why D const is annoying

Timon Gehr timon.gehr at gmx.ch
Mon Dec 12 09:49:11 PST 2011


On 12/12/2011 05:46 PM, Steven Schveighoffer wrote:
> On Mon, 12 Dec 2011 10:21:35 -0500, Timon Gehr <timon.gehr at gmx.ch> wrote:
>
>> On 12/12/2011 04:08 PM, Timon Gehr wrote:
>>> On 12/12/2011 03:46 PM, Timon Gehr wrote:
>>>> On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:
>>>>> On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi at example.org> wrote:
>>>>>
>>>>>> Am 10.12.2011 21:25, schrieb Walter Bright:
>>>>>>> On 12/10/2011 11:03 AM, Mehrdad wrote:
>>>>>>>> So how are you supposed to implement opApply on a container (or
>>>>>>>> e.g.
>>>>>>>> here, a
>>>>>>>> matrix)? Copy/paste the code for const- and non-const versions?
>>>>>>>
>>>>>>> Internal to a function, inout behaves like 'const'. You won't be
>>>>>>> able to
>>>>>>> modify the data. Therefore, if there is no inout in the return type,
>>>>>>> use
>>>>>>> 'const' in the parameter list instead.
>>>>>>>
>>>>>>> The purpose of inout is to transmit the 'constness' of the function
>>>>>>> argument type to the return type, using only one implementation of
>>>>>>> that
>>>>>>> function. That requires the function to internally regard inout as
>>>>>>> const.
>>>>>>
>>>>>> But what about:
>>>>>> void f(ref inout(int)* a, inout(int)* b) { a = b; }
>>>>>> This cant work with const because that would violate the const
>>>>>> system.
>>>>>> I think the rule should be that either the return type must be inout
>>>>>> or at least one ref/out parameter.
>>>>>> Am I overlooking something?
>>>>>
>>>>> That was brought up during discussion on adding the feature. One of
>>>>> the
>>>>> reasons inout is viable is because a) the source and result of
>>>>> where the
>>>>> constancy flows is well defined and b) the exit point is an rvalue
>>>>>
>>>>> Allowing ref parameters fails both those rules.
>>>>>
>>>>> Consider this:
>>>>>
>>>>> void bad(ref inout(int)* a, ref inout(int)* b);
>>>>>
>>>>> which is the entry and which is the exit? Is a set to b, or b set
>>>>> to a?
>>>>>
>>>>> Now, also consider that you can't affect the constancy of the result,
>>>>> because the type of the parameter is already defined. e.g.:
>>>>>
>>>>> // note that your example shouldn't even be valid, because you can't
>>>>> implicitly cast through two mutable references
>>>>> int a;
>>>>> auto pa = &a;
>>>>> immutable int b;
>>>>> auto pb = &b;
>>>>>
>>>>> f(a, b);
>>>>>
>>>>> How can this affect a? its type is already decided. Compare that to:
>>>>>
>>>>> inout(int)* g(inout(int)* b) { return b;}
>>>>>
>>>>> auto pa = g(pb);
>>>>>
>>>>> clean and simple.
>>>>>
>>>>> -Steve
>>>>
>>>> This currently compiles:
>>>>
>>>> inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;}
>>>>
>>>> void main(){
>>>> immutable int a=2;
>>>> int *x;
>>>> immutable(int)* y=&a;
>>>> very_bad(x,y);
>>>> *x=1;
>>>> assert(*y==a); // fail
>>>> }
>>>>
>>>> How does the design catch this error? Will inout** = inout**
>>>> assignments
>>>> be disallowed?
>>>
>>> Probably it is better to catch it at the call site. But if that is
>>> implemented then the requirement to have inout on the return value gets
>>> nonsensical.
>>
>> OK, got it.
>>
>> What you call 'bad' should compile:
>>
>> void bad(ref inout(int)* a, ref inout(int)* b);
>>
>> int* x;
>> immutable(int)* y;
>> const(int)* z;
>>
>> bad(x,x); // fine
>> bad(y,y); // fine
>> bad(z,z); // fine
>>
>> bad(x,y); // inout is deduced to const, ergo neither x nor y convert
>> to the parameter type -> compile error
>> bad(x,z); // now only error for x
>> ...
>>
>>
>> The requirement of having inout on the return type should be removed
>> for more expressiveness.
>
> I've thought about this for a few minutes, and I can't find a flaw in
> it. But I'm not seeing a huge benefit to it either. Why is it
> advantageous to use a ref parameter for something that should be a
> return? Can you show a good use case for this?

For anything that is both input and output, for example:
void swap(T)(ref inout(T)[] x, ref inout(T)[] y);
void apply(T)(inout(T)[] delegate(inout(T)[]) dg,  ref inout(T)[] arg);

I think it should be a no-brainer, all that is needed is to remove the 
check if inout is present on the return type. Every other part of the 
type checking is identical to when it is there (this is shown by the 
fact that qualifying void with inout makes the examples compile.)

> I still am uneasy with
> allowing applying a wildcard to a reference underneath to mutable
> references. I know it doesn't work for const.
>
> -Steve

There is nothing being applied, inout is just matched. After the 
matching, inout vanishes (it is replaced by nothing, const or immutable) 
and if that _inout-free_ parameter list still typechecks with the 
arguments, then it can be invoked. It is simply a case of parametric 
polymorphism.

void good(ref inout(int)* a, ref inout(int)* b);

is simultaneously these three functions: (duplicating the code that way 
is the poor man's inout)

void good(ref int* a, ref int* b){ a = b; }
void good(ref immutable(int)* a, ref immutable(int)* b){ a = b; }
void good(ref const(int)* a, ref const(int)* b){ a = b; }

None of these applies any type modifier at any level.

It suffices if any of those three typechecks for the call to the 
polymorphic one to succeed safely. (because the body of 'good' treats 
its arguments like const).






More information about the Digitalmars-d mailing list