December 11, 2011
Am 11.12.2011 19:00, schrieb Walter Bright:
> On 12/11/2011 9:07 AM, Mafi wrote:
>> 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?
>
> It should work with const.

But allowing a variable of type abc(int)* where abc is immutable or nothing (ie mutable) to be passed to a parameter of the type ref/out const(int)* breaks the type system.

void f(ref const(int)* a, const(int)* b) {a = b; }
void main() {
  immutable(int)* a;
  auto b = (new int[](5)).ptr;
  f(a, b);
  //if this compiles I have just assigned a mutable pointer
  //to an immutable one
}

With the above definition using inout, such a call would be disallowed.
December 11, 2011
On 12/11/11 12:40 PM, Mafi wrote:
> void f(ref const(int)* a, const(int)* b) {a = b; }
> void main() {
>    immutable(int)* a;
>    auto b = (new int[](5)).ptr;
>    f(a, b);
>    //if this compiles I have just assigned a mutable pointer
>    //to an immutable one
> }

immutable(int)* does not convert to immutable(const)*.

Andrei
December 11, 2011
On 12/11/2011 08:35 PM, Andrei Alexandrescu wrote:
> On 12/11/11 12:40 PM, Mafi wrote:
>> void f(ref const(int)* a, const(int)* b) {a = b; }
>> void main() {
>> immutable(int)* a;
>> auto b = (new int[](5)).ptr;
>> f(a, b);
>> //if this compiles I have just assigned a mutable pointer
>> //to an immutable one
>> }
>
> immutable(int)* does not convert to immutable(const)*.
>
> Andrei

I think you meant an lvalue of type immutable(int)* does not convert to ref const(int)*.
DMD currently allows it. http://d.puremagic.com/issues/show_bug.cgi?id=4251
December 11, 2011
Am 11.12.2011 20:35, schrieb Andrei Alexandrescu:
> On 12/11/11 12:40 PM, Mafi wrote:
>> void f(ref const(int)* a, const(int)* b) {a = b; }
>> void main() {
>> immutable(int)* a;
>> auto b = (new int[](5)).ptr;
>> f(a, b);
>> //if this compiles I have just assigned a mutable pointer
>> //to an immutable one
>> }
>
> immutable(int)* does not convert to immutable(const)*.
>
> Andrei
[assuming you meant ref const(int)*]

But look:
I want a function f(a,b) so that it copies int* b into a. I want this function to be const correct and not to be a template.
If I'm not mistaken, in the planned D (ie without bugs and with everything implememnted correctly) there's no way to express such a function.
I see two solutions:
1. Allow  calling void f(ref const(int)* a, const(int)* b) {a = b; } with immutable parameter a.
2. Allow void f(ref inout(int)* a, inout(int)* b) { a = b; } using my rule I suggested before.

I think Walter suggests number 1 but as I showed it violates the const system.
I think allowing inout with ref/out parameters is right even without the return value being inout. These ref/out parameters are conceptionally results.
I should also be allowed if delegate/function has a inout parameter. So void g(inout int a, delegate(inout(int)*)) should be allowed.
This delegate is a callback and it's parameter is conceptionally the result of this function.

In my opinion we definitly have to change the current strict inout-rule.

Mafi
December 11, 2011
On 11/12/11 06:55, Jonathan M Davis wrote:
> On Saturday, December 10, 2011 05:45:11 bearophile wrote:
>> Timon Gehr:
>>> Just slice the const array to get a range. The specialization for ranges
>>> does not have the bug.
>>>
>>> import std.algorithm;
>>> void main() {
>>>
>>>       const arr = [1, 2, 3];
>>>       reduce!"a*b"(arr[]);   // It works.
>>>
>>> }
>> Wasn't arr a range already?
> Per isInputRange!(typeof(arr)), no. It has the right functions, but it can't
> use them, because it's const. A const range is essentially useless, because
> you can't iterate over it.
>
> When a template is instantiated, it's instantiated on the exact types that
> it's given. So, if you given a const or immutable array, it's going to
> instantiate on that type, even though it _could_ theoretically instantiate
> itself with a mutable array with const or immutable elements. And since, a
> const or immutable array won't work as a range, the template instantiation
> fails.
>
> The range-based functions in std.array and std.string work with const and
> immutable arrays simply because they are either coded specifically for arrays
> or have specializations for them. You need a function which takes const(T)[]
> rather than one which takes const T[]. std.array and std.string typically take
> const(T)[] rather than const T[], whereas a more general range function is
> taking R, which in the case above is determined to be const int[], which won't
> work as a range.
>
> It used to be that the slice of an array was the exact type of that array,
> meaning Timon's solution wouldn't work without a cast, because the slice would
> be just as const as the original. Fortunately, that has been changed, so now a
> slice of a const or immutable array will have its elements be const or
> immutable but not the slice itself. So, now const and immutable arrays are
> like static arrays in that you need to slice them to use them with range-based
> functions, but you can't use them directly with range-based functions.
>
> A bigger problem is const or immutable ranges which are structs. Unless the
> programmer who defined the range type managed to have an opSlice which returned
> a version with const or immutable elements but where the range itself wasn't
> const or immutable (i.e. a tail-const slice), then you can't even get slicing
> to work. And even if templates were improved to the point that they would
> instantiate const int[] as const(int)[] (which I expect is a change which will
> never happen due to the difficulties in doing so), that wouldn't solve the
> problem for ranges which aren't arrays.
Instead of an immutable struct trying to be a range, why not have it return a mutable range over its immutable elements? This would be analogous to slicing an immutable array.
> Also, const and immutable ranges which aren't arrays which have no opSlice
> will _never_ work, because there's no way to get a mutable slice of them to
> operate on, so you're stuck with a const or immutable range.
>
> Really what this means is that you need to slice const and immutable ranges
> when you pass them to range-based functions, and then as long as they're
> arrays or their opSlices have been defined properly, it'll work just fine.
>
> If ranges had been designed more like slists (i.e. they have head and tail
> rather than front and popFront), then they would have been inherently tail-
> const and we wouldn't be having these problems. But that's less efficent,
> because you have to copy the range every time that you want to remove an
> element from it. Regardless, it's too late now. Ranges are too heavily used
> with their current API for us to change it that drastically now.
>
> - Jonathan M Davis


-- 
Graham St Jack

December 12, 2011
On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi@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
December 12, 2011
On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:
> On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi@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?
December 12, 2011
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@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.
December 12, 2011
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@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.







December 12, 2011
On Mon, 12 Dec 2011 10:21:35 -0500, Timon Gehr <timon.gehr@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@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?  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