December 12, 2011
On 12/12/2011 05:46 PM, Steven Schveighoffer wrote:
> 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?

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).




December 12, 2011
On Mon, 12 Dec 2011 12:49:11 -0500, Timon Gehr <timon.gehr@gmx.ch> wrote:

> On 12/12/2011 05:46 PM, Steven Schveighoffer wrote:

>> I still am uneasy with
>> allowing applying a wildcard to a reference underneath to mutable
>> references. I know it doesn't work for const.
>
> 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).

This is a great way to think about inout in general:

1. all inout parmeters are matched against int, immutable, and inout.  If all of them match that qualifier, then that qualifier is substituted for inout.
2. If 1. does not match any of those, const substituted for inout.
3. Normal function call rules apply.  That is, just because a match is found doesn't mean the function can be called (as you have demonstrated).
4. Anything that is inout inside a function is treated the same as it is now (similarly to immutable, except for const(inout(...)) ).

I always thought of inout as being something performed by the compiler, while calling the function.  But it's almost just a way to repaint the parameters/return type of an existing function before applying normal function call rules.

I'm cautiously optimistic about this.  I want to see what Kenji thinks, at this point he probably understands inout better than me.  I think actually his first patch did not require inout on the return type, and I insisted it was required, that might have been a mistake to do that (removing this requirement indirectly fixes 6809 as well).

-Steve
December 12, 2011
On 12/10/2011 03:52 AM, Timon Gehr wrote:
> On 12/10/2011 11:45 AM, 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?
>>
>> Bye,
>> bearophile
>
> No, popFront is mutating and const(int[]) cannot be mutated.

Seems to me like popFront isn't the right tool for the job.
December 12, 2011
On Monday, December 12, 2011 12:13:54 Kai Meyer wrote:
> On 12/10/2011 03:52 AM, Timon Gehr wrote:
> > On 12/10/2011 11:45 AM, 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?
> >> 
> >> Bye,
> >> bearophile
> > 
> > No, popFront is mutating and const(int[]) cannot be mutated.
> 
> Seems to me like popFront isn't the right tool for the job.

??? That's how you iterate a range. Without it, a range is essentially useless. There _is_ no other tool for the job.

Ranges _could_ have been designed more like slists and use head and tail and avoid needing to be mutated, but that's less efficient, since you have to copy the range every time that you iterate to the next item.

Generally, the way to solve the issue of a const or immutable range is to get a tail-const copy of it. With arrays, that's easy - just slice it. With user- defined ranges, it requires that opSlice be defined that way, which it may or may not be.

But you can't iterate over a const range anymore than you can iterate over a const iterator in C++. In both cases, you need a non-const copy if you want to iterate. There's nothing odd about it really. It's just that templates take the _exact_ type with IFTI and so don't generally work with const or immutable ranges. The proposed change to make arrays instantiate templates with their tail-const type with IFTI will fix that for arrays though.

- Jonathan M Davis
December 13, 2011
On Sun, 11 Dec 2011 19:40:42 +0100
Mafi <mafi@example.org> 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
> }
> 
> With the above definition using inout, such a call would be disallowed.

Just gave this code a try:

void f(ref const(int)* a, const(int)* b) {a = b; }
void main() {
   const(int)* a;
   immutable(int)* b;
   auto c = (new int[](5)).ptr;
   f(a, c);
   f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue
}
December 13, 2011
On 12/13/2011 01:58 AM, Jesse Phillips wrote:
> On Sun, 11 Dec 2011 19:40:42 +0100
> Mafi<mafi@example.org>  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
>> }
>>
>> With the above definition using inout, such a call would be
>> disallowed.
>
> Just gave this code a try:
>
> void f(ref const(int)* a, const(int)* b) {a = b; }
> void main() {
>     const(int)* a;
>     immutable(int)* b;
>     auto c = (new int[](5)).ptr;
>     f(a, c);
>     f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue
> }

Interesting. That is another bug that masks the one you were trying to exploit. :o) Consider:

void f(const ref int* a) { }
void main() {
    immutable(int)* a;
    f(a); // same error, but should compile
}

Anyway this does the same bad stuff as your example was trying to do and currently compiles:

void f(const(int)** a, const(int)* b) {*a = b; }
void main() {
    const(int)* a;
    immutable(int)* b;
    auto c = (new int[](5)).ptr;
    f(&a, c);
    f(&b, c); // now goes through
}






April 28, 2012
Okay, final exams are coming up again, and so are my bugs (I have no idea what the correlation is, don't ask...)
I guess I should post this on bugzilla, but oh well... I continued the thread instead.

Try compiling this (I did this on Windows, DMD 2.059):

void main() { Foo!(int[])([[1], [2]]); }
struct Foo(T) { auto foo() { Foo!(T[]) t; return t; } } 

April 28, 2012
Oh sorry, here's the previous thread... somehow it got detached because of the subject line change:
http://lists.puremagic.com/pipermail/digitalmars-d/2011-December/117172.html 

April 28, 2012
On Saturday, 28 April 2012 at 04:45:59 UTC, Mehrdad wrote:
> Okay, final exams are coming up again, and so are my bugs (I have no idea what the correlation is, don't ask...)
> I guess I should post this on bugzilla, but oh well... I continued the thread instead.
>
> Try compiling this (I did this on Windows, DMD 2.059):
>
> void main() { Foo!(int[])([[1], [2]]); }
> struct Foo(T) { auto foo() { Foo!(T[]) t; return t; } }

You expected that to work? Extra extra, infinite recursion is infinite! You are asking the compiler to instantiate Foo with the type int[], then use that type to instantiate Foo with int[][], which then instantiates Foo with type int[][][].

Try thinking about your code before mouthing off here. Would you fault C for causing a stack overflow in this case:

int rec(int a){
  return rec(a + 1);
}

I mean what did you expect, that the compiler could magically create infinite types? I can't even see where you might have gone wrong here, since the code is so simple. What I can see is that the constructor wouldn't work because there are no fields. I can see that you have some very strange ideas about templates, Foo(T) instantiates Foo(T[]), which is a different type, so it goes through and instantiates Foo(T[][]) which is, again, a different type.

Think before declaring D to have bugs.

--
James Miller
April 28, 2012
> You expected that to work?

Uhm, why not?

template<class T>
struct F
{
   F<F<T> > f() { return F<F<T> >(); }
};

int main()
{
   F<int>().f().f().f().f().f();  // etc.
   return 0;
}



> Try thinking about your code before mouthing off here.
Not trying to to be rude, but did you think about *your* reason before responding?