www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - transporting qualifier from parameter to the return value

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Time has come to make a decision on implementing Steven Schveighoffer's 
proposal:

http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP2

It's been in the back of my mind for a while now, I don't find a fault 
with it, it solves a very unpleasant problem that would seriously mar 
qualifiers, and although it doesn't solve 100% of the potential cases, 
it does solve a good fraction of them. I am confident that we can 
require body duplication for the remaining stragglers with a straight face.

My main concern is allowing shared to participate to the inout 
transportation. I am tempted to allow it, but shared is constrained much 
more severely than the other two.

To avoid problems with the existing uses of inout (which is an old 
synonym for ref), the language requires that you redundantly use inout 
in the return type as well. We could eliminate that in D3.

Any thoughts would be appreciated.


Andrei
Dec 15 2009
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 15 Dec 2009 22:02:59 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Time has come to make a decision on implementing Steven Schveighoffer's  
 proposal:

 http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP2

I'll note that this is not my proposal, my original proposal is in the referenced bug 1961. I did modify the DIP a bit.
 My main concern is allowing shared to participate to the inout  
 transportation. I am tempted to allow it, but shared is constrained much  
 more severely than the other two.

It doesn't work, because shared doesn't implicitly cast to const. During the function body, the parameters tagged with inout are treated as if they were tagged as const. It works for immutable, const and mutable because all those implicitly cast to const. Shared isn't the same, because it requires synchronization even when viewed using const. In addition, if you store a pointer to something that is const during such a function, it is valid after the function exits, a pointer to something shared has to go back to being shared after the function exits, it can't remain as const. I think, however, this may not be much of an issue. I don't think there are as many use cases for data types that are meant to be both shared and unshared than there are data types meant to be *only* shared or unshared.
 To avoid problems with the existing uses of inout (which is an old  
 synonym for ref), the language requires that you redundantly use inout  
 in the return type as well. We could eliminate that in D3.

2 points here: 1. inout is not necessarily used on the *entire* return type. For instance, I'd consider inout(T)[] to be the most useful return type for array processing functions, not inout T[]. It would be difficult to eliminate this requirement and still allow such specificity. However, we already have a way to "eliminate" it -- auto. 2. the choice of inout is not my first choice, I'd prefer a new keyword. The inout compromise was meant to subvert the "we already have too many keywords" argument (it was Janice's idea). If there are no objections, I prefer what the DIP proposed, vconst. All I'm saying is, reusing inout is *not* a very important part of the proposal. -Steve
Dec 15 2009
next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-12-15 22:41:19 -0500, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 2. the choice of inout is not my first choice, I'd prefer a new 
 keyword.   The inout compromise was meant to subvert the "we already 
 have too many  keywords" argument (it was Janice's idea).  If there are 
 no objections, I  prefer what the DIP proposed, vconst.  All I'm saying 
 is, reusing inout is  *not* a very important part of the proposal.

Seconded. In fact, we could just remove inout from the keyword list if we care about not augmenting the number of keywords. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 15 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Michel Fortin wrote:
 On 2009-12-15 22:41:19 -0500, "Steven Schveighoffer" 
 <schveiguy yahoo.com> said:
 
 2. the choice of inout is not my first choice, I'd prefer a new 
 keyword.   The inout compromise was meant to subvert the "we already 
 have too many  keywords" argument (it was Janice's idea).  If there 
 are no objections, I  prefer what the DIP proposed, vconst.  All I'm 
 saying is, reusing inout is  *not* a very important part of the proposal.

Seconded. In fact, we could just remove inout from the keyword list if we care about not augmenting the number of keywords.

Regardless of legacy, I personally find "inout" more suggestive - the qualifier goes from input to output. vconst doesn't quite tell me anything. I don't even know what "v" stands for. Andrei
Dec 15 2009
parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-12-15 23:04:38 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 Michel Fortin wrote:
 On 2009-12-15 22:41:19 -0500, "Steven Schveighoffer" 
 <schveiguy yahoo.com> said:
 
 2. the choice of inout is not my first choice, I'd prefer a new 
 keyword.   The inout compromise was meant to subvert the "we already 
 have too many  keywords" argument (it was Janice's idea).  If there are 
 no objections, I  prefer what the DIP proposed, vconst.  All I'm saying 
 is, reusing inout is  *not* a very important part of the proposal.

Seconded. In fact, we could just remove inout from the keyword list if we care about not augmenting the number of keywords.

Regardless of legacy, I personally find "inout" more suggestive - the qualifier goes from input to output. vconst doesn't quite tell me anything. I don't even know what "v" stands for.

autoconst? It goes in the same vein as "auto ref" Walter just introduced to do mostly the same thing with ref (but limited to function templates). -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 16 2009
parent bearophile <bearophileHUGS lycos.com> writes:
Michel Fortin:
 autoconst?
 
 It goes in the same vein as "auto ref" Walter just introduced to do 
 mostly the same thing with ref (but limited to function templates).

Or: "auto const" Bye, bearophile
Dec 16 2009
prev sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 I think, however, this may not be much of an issue.  I don't think there 
 are as many use cases for data types that are meant to be both shared 
 and unshared than there are data types meant to be *only* shared or 
 unshared.

I agree.
Dec 15 2009
prev sibling next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-12-15 22:02:59 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 Time has come to make a decision on implementing Steven Schveighoffer's 
 proposal:
 
 http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP2
 
 It's been in the back of my mind for a while now, I don't find a fault 
 with it, it solves a very unpleasant problem that would seriously mar 
 qualifiers, and although it doesn't solve 100% of the potential cases, 
 it does solve a good fraction of them. I am confident that we can 
 require body duplication for the remaining stragglers with a straight 
 face.
 
 My main concern is allowing shared to participate to the inout 
 transportation. I am tempted to allow it, but shared is constrained 
 much more severely than the other two.
 
 To avoid problems with the existing uses of inout (which is an old 
 synonym for ref), the language requires that you redundantly use inout 
 in the return type as well. We could eliminate that in D3.
 
 Any thoughts would be appreciated.

inout? I guess you mean you decided to rename vconst by inout. I can't seem to find a better name, so I guess it's good enough. But I see a flaw in accepting shared. And vconst might be a better name after all. Here's the explanation. So what code inout would generate now? Generate one copy of the function for each possible combination? It can't really do otherwise because the const and non-const versions of other functions it calls might be separate functions too, with separate addresses: class Test { void doThat() immutable {} void doThat() const {} void doThat() {} void doThat() shared immutable {} void doThat() shared const {} void doThat() shared {} void doSomething() inout // or vconst { doThat(); // which doThat does it call? } } Let's accept that doSomething is generated in six copies to allow doSomething to call each version of doThat, and let's look at the next case: class Test { void doThat() immutable {} void doThat() shared immutable {} void doSomething() inout // or vconst { doThat(); // which doThat does it call? } } Now you also have the problem that only the shared immutable and non-shared immutable version of doSomething can compile. Should it generate only two functions? What if I remove the body and only the signature remains, how do I know I can't call doSomething on a mutable object? The problem here is conflating constness with sharedness (!). With vconst, you know it can accept any kind of constness, so it's automatically an error if you can't compile the immutable, the mutable and the const version of the function. Mix shared into this and everything is mixed up. So to catch errors correctly you need vconst which would instanciate three versions of the function: mutable, const, immutable, and be an error if one of the three can't compile; and vshared which would instanciate two: thread-local and shared, and be an error if one of the two can't compile. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 15 2009
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 15 Dec 2009 22:43:55 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2009-12-15 22:02:59 -0500, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> said:

 Time has come to make a decision on implementing Steven Schveighoffer's  
 proposal:
  http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP2
  It's been in the back of my mind for a while now, I don't find a fault  
 with it, it solves a very unpleasant problem that would seriously mar  
 qualifiers, and although it doesn't solve 100% of the potential cases,  
 it does solve a good fraction of them. I am confident that we can  
 require body duplication for the remaining stragglers with a straight  
 face.
  My main concern is allowing shared to participate to the inout  
 transportation. I am tempted to allow it, but shared is constrained  
 much more severely than the other two.
  To avoid problems with the existing uses of inout (which is an old  
 synonym for ref), the language requires that you redundantly use inout  
 in the return type as well. We could eliminate that in D3.
  Any thoughts would be appreciated.

inout? I guess you mean you decided to rename vconst by inout. I can't seem to find a better name, so I guess it's good enough. But I see a flaw in accepting shared. And vconst might be a better name after all. Here's the explanation. So what code inout would generate now? Generate one copy of the function for each possible combination? It can't really do otherwise because the const and non-const versions of other functions it calls might be separate functions too, with separate addresses: class Test { void doThat() immutable {} void doThat() const {} void doThat() {} void doThat() shared immutable {} void doThat() shared const {} void doThat() shared {} void doSomething() inout // or vconst { doThat(); // which doThat does it call? } }

First, the proposal as I see it is to compile *one* copy of doSomething (see points 5 and 6 in the DIP description). Second, inout (or vconst) implicitly casts to const, so it can call any functions that require const *or* that require inout. Third, and this is kind of a nitpick, inout functions should *require* inout in the return type, otherwise, there's no point, the qualifier could just be const :) So your example is technically invalid. So to answer your question, doSomething can only call: void doThat() const {} and I don't think shared can be involved because there is no common type you can implicitly cast const, immutable, mutable and shared to. One further nitpick, shared immutable is useless :) -Steve
Dec 15 2009
next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2009-12-15 23:02:36 -0500, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 First, the proposal as I see it is to compile *one* copy of doSomething 
  (see points 5 and 6 in the DIP description).  Second, inout (or 
 vconst)  implicitly casts to const, so it can call any functions that 
 require const  *or* that require inout.

I see. Well that could work, as long as we don't mix shared with it.
 Third, and this is kind of a nitpick, inout  functions should *require* 
 inout in the return type, otherwise, there's no  point, the qualifier 
 could just be const :)  So your example is  technically invalid.

Should it? I think it'd be better to allow it anyway for generic programming... or at least allow inout(void) as the return type. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 15 2009
prev sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 One further nitpick, shared immutable is useless :)

There is no shared immutable type, attempts to create one simply produce "immutable".
Dec 15 2009
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 15 Dec 2009 23:21:49 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2009-12-15 23:02:36 -0500, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 Third, and this is kind of a nitpick, inout  functions should *require*  
 inout in the return type, otherwise, there's no  point, the qualifier  
 could just be const :)  So your example is  technically invalid.

Should it? I think it'd be better to allow it anyway for generic programming... or at least allow inout(void) as the return type.

That's a very good point. My preference would then be to say that it's fine to allow not having inout in the return type for the sake of generic programming. Seems similar to allowing returning the result of a void function inside a void function. -Steve
Dec 15 2009
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 15 Dec 2009 23:04:38 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Michel Fortin wrote:
 On 2009-12-15 22:41:19 -0500, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 2. the choice of inout is not my first choice, I'd prefer a new  
 keyword.   The inout compromise was meant to subvert the "we already  
 have too many  keywords" argument (it was Janice's idea).  If there  
 are no objections, I  prefer what the DIP proposed, vconst.  All I'm  
 saying is, reusing inout is  *not* a very important part of the  
 proposal.

we care about not augmenting the number of keywords.

Regardless of legacy, I personally find "inout" more suggestive - the qualifier goes from input to output. vconst doesn't quite tell me anything. I don't even know what "v" stands for.

"virtual" const :) My original proposal called the technique "Scoped" const, which I think is pretty accurate, since the data is const only for the scope of the function. perhaps sconst? or aconst for "any" const? In any case inout is fine by me if that's what gits 'er done. The only problem I see with inout is that it has legacy issues. -Steve
Dec 15 2009
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Andrei Alexandrescu wrote:
 Time has come to make a decision on implementing Steven Schveighoffer's 
 proposal:

Unmentioned in the proposal is is inout a type constructor or a storage class? For example, U[inout(T)]* foo(inout(X)*** p) { ... } This is much more complex to implement than only allowing inout at the top level, i.e. as a storage class. I also prefer the idea of inout on the return type being assumed, rather than explicit: T foo(inout U p) { ... } Yes, there's the legacy compatibility issue there. A long time ago, I suggested using the keyword 'return' for that, instead of inout: T foo(return U p) { ... } which looks fine until you use it for member functions: T foo() return { ... } ugh.
Dec 15 2009
next sibling parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Walter Bright (newshound1 digitalmars.com)'s article
 Andrei Alexandrescu wrote:
 Time has come to make a decision on implementing Steven Schveighoffer's
 proposal:

class? For example, U[inout(T)]* foo(inout(X)*** p) { ... }

I think it's got to be a storage class, or it's only a partial solution. If your above example doesn't work, people will still be forced to resort to all kinds of kludges like using templates and plain old code duplication.
 I also prefer the idea of inout on the return type being assumed, rather
 than explicit:
      T foo(inout U p) { ... }

Not sure how much sense that would make if we go the full storage class route, for example in your above example. It seems like this would be hard to infer. If we go the top level only route, then it seems like a worthwhile thing to save some syntactic noise.
Dec 15 2009
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 15 Dec 2009 23:43:58 -0500, Walter Bright  
<newshound1 digitalmars.com> wrote:

 Andrei Alexandrescu wrote:
 Time has come to make a decision on implementing Steven Schveighoffer's  
 proposal:

Unmentioned in the proposal is is inout a type constructor or a storage class? For example, U[inout(T)]* foo(inout(X)*** p) { ... }

type constructor. it has no meaning as a storage class since it's entirely transient (it only has any meaning inside functions). You should never be able to declare any part of a global or member variable as inout, because its meaning can change once a function exits. It's almost a beast of it's own, it's mostly like const, but has some quirky extra rules. BTW, I'm unsure if U[inout(T)] should work. Can one implicitly cast U[T] to U[const(T)] ? If not, then it doesn't make sense that U[inout(T)] should even compile. I guess the rule should be if you have a type that contains an inout notation, the complier should verify that if you define 2 types, one that removes all inout decorations, called M, and one that replaces all inout decorations with const, called N, you should be able to cast from M to N implicitly. If not, then the inout type is invalid. For example: int[inout(char)[]]: int[char[]] => M int[const(char)[]] => N
 This is much more complex to implement than only allowing inout at the  
 top level, i.e. as a storage class.

A storage class does not give you the transitivity that you need to enforce const rules. The inout tag must be carried forth to aliases of the same data.
 I also prefer the idea of inout on the return type being assumed, rather  
 than explicit:

      T foo(inout U p) { ... }

What about this? T[] foo(inout(U)[] p) {...} Should the return type implicitly be inout(T)[] or inout T[] ? -Steve
Dec 15 2009
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 type constructor.  it has no meaning as a storage class since it's 
 entirely transient (it only has any meaning inside functions).

I meant does it only apply at the top level, or does it apply down inside types?
 BTW, I'm unsure if U[inout(T)] should work.

I just meant it as a compound type. It could as easily be: bar!(inout(T)) foo(inout X) { ... }
 This is much more complex to implement than only allowing inout at the 
 top level, i.e. as a storage class.

enforce const rules. The inout tag must be carried forth to aliases of the same data.

I know, but I know how to make it work. There would be problems, though, ensuring that this works: T[] foo(inout T[]) { return "hello"; }
 What about this?
 
 T[] foo(inout(U)[] p) {...}
 
 Should the return type implicitly be inout(T)[] or inout T[] ?

It would have to be inout(T[])
Dec 15 2009
next sibling parent Jason House <jason.james.house gmail.com> writes:
Walter Bright Wrote:

 Steven Schveighoffer wrote:
 type constructor.  it has no meaning as a storage class since it's 
 entirely transient (it only has any meaning inside functions).

I meant does it only apply at the top level, or does it apply down inside types?

It has to apply inside types unless we can prove it isn't needed. Consider a function that accepts inout(T) and returns inout(U). The extraction of U from const(T) is const(U) just like extraction of U from immutable(T) is immutable(U) because of transitivity rules. I don't think a proof exists. Consider class T { U u; } and other more deeply nested variations.
Dec 16 2009
prev sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 I've been giving this some thought.  inout is a strange beast, and it 
 has some interesting rules.

You're right. Considerable care has to be taken getting the implicit conversion rules right. Some of the cases are fairly subtle.
Dec 19 2009
prev sibling next sibling parent Kagamin <spam here.lot> writes:
Walter Bright Wrote:

 Unmentioned in the proposal is is inout a type constructor or a storage 
 class? For example,
 
      U[inout(T)]* foo(inout(X)*** p) { ... }
 
 This is much more complex to implement than only allowing inout at the 
 top level, i.e. as a storage class.

It's a type constructor just like const and immutable. As to your example, imagine inout expands as follows U[T]* foo(X*** p) { ... } U[const(T)]* foo(const(X)*** p) { ... } U[immutable(T)]* foo(immutable(X)*** p) { ... } If this works, then it's ok. Giving it a quick thought, it should work.
 I also prefer the idea of inout on the return type being assumed, rather 
 than explicit:
 
      T foo(inout U p) { ... }

Given inout is a type constructor, it's critical for type safety to properly decorate return type: T is mutable and can't play role of inout-qualified type.
Dec 16 2009
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 16 Dec 2009 00:57:59 -0500, Walter Bright  
<newshound1 digitalmars.com> wrote:

 Steven Schveighoffer wrote:
 type constructor.  it has no meaning as a storage class since it's  
 entirely transient (it only has any meaning inside functions).

I meant does it only apply at the top level, or does it apply down inside types?

I've been giving this some thought. inout is a strange beast, and it has some interesting rules. For example, let's take a struct with an already const member: struct S { const(char)[] str; } Now, we have a function that uses inout on S: inout(char)[] getStr(inout S s) { return s.str; } This should NOT compile, because if you pass in a mutable or immutable S, then the compiler casts the result back to mutable or immutable! So inout is transitive only for mutable members of data types. It's sort of similar to applying const to a struct with an immutable member, the const doesn't apply to the immutable member, only the mutable ones.
 BTW, I'm unsure if U[inout(T)] should work.

I just meant it as a compound type. It could as easily be: bar!(inout(T)) foo(inout X) { ... }

It might be workable. The only monkey wrench thrown in is that bar can do conditional compilation based on the constancy of T: template bar(T) { static if(is(T == const)) alias T[] bar; else struct bar { T t1, t2; } } This is no good, because you don't know how to build bar when your unsure if T is const or not (which is the whole point of inout). If the compiler can determine if bar!(T), bar!(const(T)), bar!(immutable(T)) and bar!(inout(T)) generate identical code (except for the constancy of T), then maybe it can forcibly cast the result once the return happens. This might be the only time where inout can be applied to member variables because it's a temporary situation inside an inout function. It's a complex situation, one that probably requires a lot of thinking. My recommendation at this time is to only allow bar!(inout(T)) if it is an alias to something that normally would be returnable/passable to an inout function e.g.: template bar(T) { alias T[] bar; } as for AA's, it might be a case where we should allow it, since we know AA's have the same implementation regardless of type info. -Steve
Dec 19 2009
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Andrei Alexandrescu wrote:
 Any thoughts would be appreciated.

The more I think about this, the more I think it cannot work unless inout is elevated to be a full type constructor, just like const, immutable, and shared are. For example: inout(char[]) foo(inout char[] p) { char[] s = p; return s; // ulp, doesn't work const char[] s = p; return s; // ulp, doesn't work immutable char[] s = p; return s; // ulp, doesn't work auto s = p; return s; // ulp, doesn't work }
Dec 15 2009
parent Eldar Insafutdinov <e.insafutdinov gmail.com> writes:
Walter Bright Wrote:

 Andrei Alexandrescu wrote:
 Any thoughts would be appreciated.

The more I think about this, the more I think it cannot work unless inout is elevated to be a full type constructor, just like const, immutable, and shared are. For example: inout(char[]) foo(inout char[] p) { char[] s = p; return s; // ulp, doesn't work const char[] s = p; return s; // ulp, doesn't work immutable char[] s = p; return s; // ulp, doesn't work auto s = p; return s; // ulp, doesn't work }

OMG, D is slowly becoming C++
Dec 16 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 16 Dec 2009 07:20:11 +0300, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 On Tue, 15 Dec 2009 23:04:38 -0500, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Michel Fortin wrote:
 On 2009-12-15 22:41:19 -0500, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 2. the choice of inout is not my first choice, I'd prefer a new  
 keyword.   The inout compromise was meant to subvert the "we already  
 have too many  keywords" argument (it was Janice's idea).  If there  
 are no objections, I  prefer what the DIP proposed, vconst.  All I'm  
 saying is, reusing inout is  *not* a very important part of the  
 proposal.

if we care about not augmenting the number of keywords.

Regardless of legacy, I personally find "inout" more suggestive - the qualifier goes from input to output. vconst doesn't quite tell me anything. I don't even know what "v" stands for.

"virtual" const :) My original proposal called the technique "Scoped" const, which I think is pretty accurate, since the data is const only for the scope of the function. perhaps sconst? or aconst for "any" const? In any case inout is fine by me if that's what gits 'er done. The only problem I see with inout is that it has legacy issues. -Steve

I suggested sameconst/shareconst. It is longer but I believe it's more descriptive, as it describes that two (or more) types share the same "constness modifier". sameconst(T)[] find(sameconst(T)[] haystack, T needle) { ... } // my solution to your inout(T)[] problem It could be shortened to same(T) but people didn't like it either. I think it should be bikeshed(T)! :)
Dec 16 2009
prev sibling next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-12-15 22:02:59 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 Time has come to make a decision on implementing Steven Schveighoffer's 
 proposal:
 
 http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP2
 
 It's been in the back of my mind for a while now, I don't find a fault 
 with it, it solves a very unpleasant problem that would seriously mar 
 qualifiers, and although it doesn't solve 100% of the potential cases, 
 it does solve a good fraction of them. I am confident that we can 
 require body duplication for the remaining stragglers with a straight 
 face.

I just realized that 'inout' could have a real use even for functions with no return value. Consider this: void doSomething(inout(Object)[] a, inout(Object)[] b) { a[0] = b[0]; // works only when objects in both arrays have the same constness. } -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 16 2009
next sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 16 Dec 2009 16:48:31 +0300, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2009-12-15 22:02:59 -0500, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> said:

 Time has come to make a decision on implementing Steven Schveighoffer's  
 proposal:
  http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP2
  It's been in the back of my mind for a while now, I don't find a fault  
 with it, it solves a very unpleasant problem that would seriously mar  
 qualifiers, and although it doesn't solve 100% of the potential cases,  
 it does solve a good fraction of them. I am confident that we can  
 require body duplication for the remaining stragglers with a straight  
 face.

I just realized that 'inout' could have a real use even for functions with no return value. Consider this: void doSomething(inout(Object)[] a, inout(Object)[] b) { a[0] = b[0]; // works only when objects in both arrays have the same constness. }

Doesn't work. doSomething("hello", "world");
Dec 16 2009
next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-12-16 08:54:03 -0500, "Denis Koroskin" <2korden gmail.com> said:

 I just realized that 'inout' could have a real use even for functions  
 with no return value. Consider this:
 
 	void doSomething(inout(Object)[] a, inout(Object)[] b) {
 		a[0] = b[0]; // works only when objects in both arrays have the same  
 constness.
 	}

Doesn't work. doSomething("hello", "world");

Well, of course it doesn't... Here's what I meant: void doSomething(inout(MyStruct)*[] a, inout(MyStruct)*[] b) { a[0] = b[0]; // works only when structs in both arrays have the same constness. } -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 16 2009
parent Michel Fortin <michel.fortin michelf.com> writes:
On 2009-12-16 14:02:35 -0500, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 On Wed, 16 Dec 2009 09:21:16 -0500, Michel Fortin  
 <michel.fortin michelf.com> wrote:
 
 Here's what I meant:
 
 	void doSomething(inout(MyStruct)*[] a, inout(MyStruct)*[] b) {
 		a[0] = b[0]; // works only when structs in both arrays have the same  
 constness.
 	}

This is dangerous. We should figure out a reason why this shouldn't compile. It's hard for me to wrap my head around it. Either the implicit cast when calling the function shouldn't work, or the assignment shouldn't work. My intuition is that the implicit cast should fail.

If you're trying to define a ruke it's simple: you can cast immutable to const only on the first level of indirection, or when there is no indirection. So basically those conversions should be allowed: immutable(MyStruct)*[] -> const(MyStruct*)[] immutable(MyStruct*)[] -> const(MyStruct*)[] immutable(MyStruct*[]) -> const(MyStruct*[]) So the above function can only be called wi
 Does this code compile today?  If so, we need to fix this.  With this  
 fixed, the inout version naturally becomes invalid.

Just did a couple of small tests. Apparently everything is all right: test.d(18): Error: cannot implicitly convert expression (a) of type immutable(int)*[] to const(int)*[] Also: test.d(18): Error: cannot implicitly convert expression (b) of type int*[] to const(int)*[] Which is right otherwise you could assign immutable values to the array others see as mutable. Also, converting from immutable(int)*[] to const(int*)[] implicitly works fine and does prevent dangerous assignments. So I don't think there is anything to fix on this front. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 16 2009
prev sibling parent reply Kagamin <spam here.lot> writes:
Denis Koroskin Wrote:

 	void doSomething(inout(Object)[] a, inout(Object)[] b) {
 		a[0] = b[0]; // works only when objects in both arrays have the same  
 constness.
 	}

Doesn't work. doSomething("hello", "world");

Yes, one more level of indirection is needed: void doSomething(inout(T)[][] a, inout(T)[][] b){ a[0]=b[0]; } though it's subject to the const bug when you pass T[][] and immutable(T)[][] which is due to the current const system.
Dec 16 2009
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Kagamin wrote:
 Yes, one more level of indirection is needed:
 
 void doSomething(inout(T)[][] a, inout(T)[][] b){ a[0]=b[0]; }
 
 though it's subject to the const bug when you pass T[][] and
 immutable(T)[][] which is due to the current const system.

Is this in bugzilla?
Dec 16 2009
parent Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 On Wed, 16 Dec 2009 13:59:36 -0500, Walter Bright 
 <newshound1 digitalmars.com> wrote:
 
 Kagamin wrote:
 Yes, one more level of indirection is needed:
  void doSomething(inout(T)[][] a, inout(T)[][] b){ a[0]=b[0]; }
  though it's subject to the const bug when you pass T[][] and
 immutable(T)[][] which is due to the current const system.

Is this in bugzilla?

See my post in another part of this thread, there are 3 related bugs in bugzilla. -Steve

Those all looked possibly different, so I put it in as: http://d.puremagic.com/issues/show_bug.cgi?id=3621
Dec 16 2009
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 16 Dec 2009 09:21:16 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2009-12-16 08:54:03 -0500, "Denis Koroskin" <2korden gmail.com> said:

 I just realized that 'inout' could have a real use even for functions   
 with no return value. Consider this:
  	void doSomething(inout(Object)[] a, inout(Object)[] b) {
 		a[0] = b[0]; // works only when objects in both arrays have the  
 same  constness.
 	}

doSomething("hello", "world");

Well, of course it doesn't... Here's what I meant: void doSomething(inout(MyStruct)*[] a, inout(MyStruct)*[] b) { a[0] = b[0]; // works only when structs in both arrays have the same constness. }

This is dangerous. We should figure out a reason why this shouldn't compile. It's hard for me to wrap my head around it. Either the implicit cast when calling the function shouldn't work, or the assignment shouldn't work. My intuition is that the implicit cast should fail. Consider the following function with today's notation: void doSomething(const(MyStruct)*[] a, const(MyStruct*)[] b) { a[0] = b[0]; } Now: immutable(MyStruct)*[] im; MyStruct*[] mut; MyStruct *s = new MyStruct; mut ~= s; im.length = 1; doSomething(im, mut); Oops, without a cast, we have changed s into an immutable struct. Hidden inside a function no less! Does this code compile today? If so, we need to fix this. With this fixed, the inout version naturally becomes invalid. -Steve
Dec 16 2009
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 16 Dec 2009 14:43:47 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2009-12-16 14:02:35 -0500, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 On Wed, 16 Dec 2009 09:21:16 -0500, Michel Fortin   
 <michel.fortin michelf.com> wrote:

 Here's what I meant:
  	void doSomething(inout(MyStruct)*[] a, inout(MyStruct)*[] b) {
 		a[0] = b[0]; // works only when structs in both arrays have the  
 same  constness.
 	}

We should figure out a reason why this shouldn't compile. It's hard for me to wrap my head around it. Either the implicit cast when calling the function shouldn't work, or the assignment shouldn't work. My intuition is that the implicit cast should fail.

If you're trying to define a ruke it's simple: you can cast immutable to const only on the first level of indirection, or when there is no indirection. So basically those conversions should be allowed: immutable(MyStruct)*[] -> const(MyStruct*)[] immutable(MyStruct*)[] -> const(MyStruct*)[] immutable(MyStruct*[]) -> const(MyStruct*[])

That sounds correct.
 Does this code compile today?  If so, we need to fix this.  With this   
 fixed, the inout version naturally becomes invalid.

Just did a couple of small tests. Apparently everything is all right: test.d(18): Error: cannot implicitly convert expression (a) of type immutable(int)*[] to const(int)*[] Also: test.d(18): Error: cannot implicitly convert expression (b) of type int*[] to const(int)*[] Which is right otherwise you could assign immutable values to the array others see as mutable. Also, converting from immutable(int)*[] to const(int*)[] implicitly works fine and does prevent dangerous assignments. So I don't think there is anything to fix on this front.

I did some testing too. This compiles, which should be a bug: import std.stdio; void myfunc(const(int)[][]a, const(int)[][]b) { a[0]= b[0]; } void main() { immutable(int)[][] i1; i1.length = 1; int [][]i2; i2 ~= [5]; myfunc(i1, i2); writefln("%d", i1[0][0]); i2[0][0] = 6; writefln("%d", i1[0][0]); } Writes: 5 6 I think it has something to do with array casting rules. Because you can cast just the pointer part of the array struct, it appears that these cases aren't considered the same as the pointer casting as you demonstrated. I'll look for a bug on this, and if not, I'll add one. -Steve
Dec 16 2009
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 16 Dec 2009 15:38:56 -0500, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:


 I'll look for a bug on this, and if not, I'll add one.

2544 (http://d.puremagic.com/issues/show_bug.cgi?id=2544) is a bug that exactly describes this problem, but it was marked as a duplicate of a more general problem in bug 2095 (http://d.puremagic.com/issues/show_bug.cgi?id=2095) apparently, 2095 is a duplicate of a very old bug 926 (http://d.puremagic.com/issues/show_bug.cgi?id=926) I think it's about time this was addressed, especially in the face of allowing unsafe operations in safe D. -Steve
Dec 16 2009
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 16 Dec 2009 13:59:36 -0500, Walter Bright  
<newshound1 digitalmars.com> wrote:

 Kagamin wrote:
 Yes, one more level of indirection is needed:
  void doSomething(inout(T)[][] a, inout(T)[][] b){ a[0]=b[0]; }
  though it's subject to the const bug when you pass T[][] and
 immutable(T)[][] which is due to the current const system.

Is this in bugzilla?

See my post in another part of this thread, there are 3 related bugs in bugzilla. -Steve
Dec 16 2009
prev sibling next sibling parent reply Leandro Lucarella <llucax gmail.com> writes:
Andrei Alexandrescu, el 15 de diciembre a las 19:02 me escribiste:
 Time has come to make a decision on implementing Steven
 Schveighoffer's proposal:
 
 http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP2
 
 It's been in the back of my mind for a while now, I don't find a
 fault with it, it solves a very unpleasant problem that would
 seriously mar qualifiers, and although it doesn't solve 100% of the
 potential cases, it does solve a good fraction of them. I am
 confident that we can require body duplication for the remaining
 stragglers with a straight face.
 
 My main concern is allowing shared to participate to the inout
 transportation. I am tempted to allow it, but shared is constrained
 much more severely than the other two.
 
 To avoid problems with the existing uses of inout (which is an old
 synonym for ref), the language requires that you redundantly use
 inout in the return type as well. We could eliminate that in D3.
 
 Any thoughts would be appreciated.

Looks like inout won: http://www.dsource.org/projects/phobos/changeset/1389 Doesn't look like a very intuitive name though (another enum? :). -- Leandro Lucarella (AKA luca) http://llucax.com.ar/ ---------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------- Es más probable que el tomate sea perita, a que la pera tomatito. -- Peperino Pómoro
Dec 18 2009
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 On Fri, 18 Dec 2009 22:18:36 -0500, Leandro Lucarella <llucax gmail.com> 
 wrote:
 
 Looks like inout won:
 http://www.dsource.org/projects/phobos/changeset/1389

 Doesn't look like a very intuitive name though (another enum? :).

Better than not having the feature :) Some notes on the to-be description: There is no mention that inout variables cannot be changed (they cannot). In the paragraph describing what inout resolves to on return, if all matches are inout, then the return type should be set to inout, not const. This is important for calling inout functions from within inout functions. I'm really excited to finally see this coming to fruition! Thanks Walter and Andrei!

Thank you for your detailed inout proposal and for making us understand the details of what it takes to define inout. Andrei
Dec 19 2009
prev sibling parent reply Jason House <jason.james.house gmail.com> writes:
Steven Schveighoffer Wrote:

 On Fri, 18 Dec 2009 22:18:36 -0500, Leandro Lucarella <llucax gmail.com>  
 wrote:
 
 Looks like inout won:
 http://www.dsource.org/projects/phobos/changeset/1389

 Doesn't look like a very intuitive name though (another enum? :).

Better than not having the feature :) Some notes on the to-be description: There is no mention that inout variables cannot be changed (they cannot). In the paragraph describing what inout resolves to on return, if all matches are inout, then the return type should be set to inout, not const. This is important for calling inout functions from within inout functions. I'm really excited to finally see this coming to fruition! Thanks Walter and Andrei! -Steve

The docs should also discuss inout variables declared within the function (to store intermediate results while processing input arguments). The meaning of inout by a nested function isn't obvious when the enclosing function is already using inout. Does inout of the nested function match that of the enclosing function? Or are they distinct. If distinct, there may semantically ambiguous cases...
Dec 19 2009
parent Jason House <jason.james.house gmail.com> writes:
Steven Schveighoffer Wrote:

 On Sat, 19 Dec 2009 18:35:13 -0500, Jason House  
 <jason.james.house gmail.com> wrote:
 
 The meaning of inout by a nested function isn't obvious when the  
 enclosing function is already using inout. Does inout of the nested  
 function match that of the enclosing function? Or are they distinct. If  
 distinct, there may semantically ambiguous cases...

One thing that inout does is [snip of very long, but off topic reply :(]

I meant functions nested inside of functions. inout(A) foo(inout B b, inout C c){ inout(D) bar(inout E e, inout F f){ ... } ... }
Dec 20 2009
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 18 Dec 2009 22:18:36 -0500, Leandro Lucarella <llucax gmail.com>  
wrote:

 Looks like inout won:
 http://www.dsource.org/projects/phobos/changeset/1389

 Doesn't look like a very intuitive name though (another enum? :).

Better than not having the feature :) Some notes on the to-be description: There is no mention that inout variables cannot be changed (they cannot). In the paragraph describing what inout resolves to on return, if all matches are inout, then the return type should be set to inout, not const. This is important for calling inout functions from within inout functions. I'm really excited to finally see this coming to fruition! Thanks Walter and Andrei! -Steve
Dec 18 2009
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sat, 19 Dec 2009 18:35:13 -0500, Jason House  
<jason.james.house gmail.com> wrote:

 The docs should also discuss inout variables declared within the  
 function (to store intermediate results while processing input  
 arguments).

Yes, good point. Also important is that inout can ONLY be used on stack variables, it can never be used in global or class member variables.
 The meaning of inout by a nested function isn't obvious when the  
 enclosing function is already using inout. Does inout of the nested  
 function match that of the enclosing function? Or are they distinct. If  
 distinct, there may semantically ambiguous cases...

One thing that inout does is it makes the function not really have to care what the calling 'const match' is, because the compiler takes care of that detail at the call site. So the compiled function isn't any different depending on the const match, just the meaning of the return value is different. Here is an example: class Foo { private Bar b; property inout(Bar) bee() inout { return b; } } class Baz { private Foo f; property inout(Bar) getBOfFoo() inout { inout(Bar) b = f.bee; // the 'const match' is inout, because at this point f is inout. return b; } } Notice that when compiling getBOfFoo, the compiler doesn't have to care what inout(Bar) means in order to call f.bee, it just knows that because f is inout, the const match is inout. It's not like it needs to recompile Foo.bee with a new const match, it just allows the result to be implicitly cast to inout. As long as the rules are followed, it doesn't have to be complicated. It's like a template, but where all the instantiations are identical, just the semantic meaning of the return is parameterized. The rules are carefully designed to make the implementation simple and intuitive, but allow precise const protection where it is needed. Another example, showing 2 different types of inout with a nested call: inout(char)[] trim(inout(char)[] input) { while(input.length != 0 && isspace(input[0])) input = input[1..$]; while(input.length != 0 && isspace(input[$-1])) input = input[0..$-1]; return input; } inout(char)[][] split(inout(char)[] src, const(char)[] delimiter) { delimiter = trim(delimiter); // const match for this one call is const, so it's valid to reassign back to const. // inout(char)[] d = trim(delimiter); // illegal, because the const match is const, and const does not implicitly cast to inout. int i; inout(char)[][] result; while((i = src.indexOf(delimiter)) != src.length) { result ~= src[0..i]; src = src[i + delimiter.length..$]; } result ~= src; return result; } Now, split can be called with a mutable, const, immutable, or even inout src, and it doesn't alter your contract on src's data :) The signature says "src is whatever you want it to be, the constancy will be forwarded to the return value, and I promise not to molest src's data. delimiter will not be molested, but its constancy will not play a part in the return value." The cool part about it is that it just works the way you want it to, and provides the protection you want it to. It gives you const protection without the viral nature of const that people dislike. -Steve
Dec 19 2009
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 20 Dec 2009 07:58:06 -0500, Jason House  
<jason.james.house gmail.com> wrote:

 Steven Schveighoffer Wrote:

 On Sat, 19 Dec 2009 18:35:13 -0500, Jason House
 <jason.james.house gmail.com> wrote:

 The meaning of inout by a nested function isn't obvious when the
 enclosing function is already using inout. Does inout of the nested
 function match that of the enclosing function? Or are they distinct.  

 distinct, there may semantically ambiguous cases...

One thing that inout does is [snip of very long, but off topic reply :(]

I meant functions nested inside of functions. inout(A) foo(inout B b, inout C c){ inout(D) bar(inout E e, inout F f){ ... } ... }

OK, I get what you are saying now, sorry for the off-topic reply. That would pose a confusing prospect. Since we only have one inout keyword, we cannot allow 2 meanings for it. So either we have to disallow inout functions as nested functions, or color the inout portions of the outer function as const implicitly while inside the inner function. I think the most useful choice is the latter. If you want any of the inout variables of the outer function to participate in choosing the inout constancy of the inner function, then they must be explicitly passed. Thanks for clarifying, that is an important thing to get right. -Steve
Dec 20 2009