www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - pointers, functions, and uniform call syntax

reply "monarch_dodra" <monarchdodra gmail.com> writes:
I was playing around with a very big struct, and told myself I 
wanted it allocated on the heap. This meant I was now 
manipulating S* instead of an S.

I thought "this should have zero impact, because in D, because 
"." is supposed to deference for you if needed."

I find it strange though that when trying to call a function with 
a pointer, the pointer isn't automatically dereferenced if needed:

----
struct S
{
   void foo();
}
void bar(S);
void main()
{
   auto r = new S;
   r.foo();
   bar(r);  //derp
   r.bar(); //derp
};
----
I find it strange, because I thought the entire point was to 
abstract way something was allocated to the way it was used: EG. 
 From a caller perspective, I don't care if r is on the stack or 
on the heap: I just want to call the method bar on the object in 
question.

Why does one consider a "free standing" function more ambiguous 
than a member function?

Things get even stranger if you mix in uniform call syntax. 
"r.foo()" works, but "r.bar()" doesn't?

Am I really forced into:
----
struct S
{
   void foo();
}
void bar(S);
void main()
{
   auto r = new S;
   r.foo();
   bar(*r);    //Groan
   (*r).bar(); //Super Groan.
};
----
I feel as if I'm just back at square 1...
Sep 03 2012
next sibling parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
 I thought "this should have zero impact, because in D, because 
 "." is supposed to deference for you if needed."
D can't do this all the time: struct pointer assignment for example, should not automatically cause the corresponding structs to be assigned.
 ----
 struct S
 {
   void foo();
 }
 void bar(S);

 void main()
 {
   auto r = new S;
   r.foo();
   bar(r);  //derp
   r.bar(); //derp
 };
 ----
 I find it strange, because I thought the entire point was to 
 abstract way something was allocated to the way it was used: 
 EG. From a caller perspective, I don't care if r is on the 
 stack or on the heap: I just want to call the method bar on the 
 object in question.
bar(r) would need D to support an implied conversion of S* to S (or to ref S with bar having a reference parameter if you wanted to avoid copying), for this to work. Converting S* to ref S (without copying) is an interesting idea for D. I wonder what those close to the definition of D and the compiler think of it.
 Things get even stranger if you mix in uniform call syntax. 
 "r.foo()" works, but "r.bar()" doesn't?

 Am I really forced into:
 ----
 struct S
 {
   void foo();
 }
 void bar(S);
 void main()
 {
   auto r = new S;
   r.foo();
   bar(*r);    //Groan
   (*r).bar(); //Super Groan.
 };
 ----
 I feel as if I'm just back at square 1...
You could fake it: D has 'alias this' which helps a lot. import std.stdio; struct Sdata { int x[10000]; //imagine this inline in the struct this(this) { writeln("Copied!"); } void foo() { writeln("foo: ", x[99]); } } struct S { Sdata* ptr; alias ptr this; this(Sdata* newSdata) { ptr = newSdata; } } ref int bar(S s) { s.x[99] = 3142; return s.x[99]; } void main() { auto r = S(new Sdata); bar(r); writeln("main: ", r.x[99] ); r.bar = 999; writeln("main: ", r.x[99] ); r.foo(); }
Sep 03 2012
parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
 ----
 struct S
 {
  void foo();
 }
 void bar(S);

 void main()
 {
  auto r = new S;
  r.foo();
  bar(r);  //derp
  r.bar(); //derp
 };
 ----
bar(r) would need D to support an implied conversion of S* to S (or to ref S with bar having a reference parameter if you wanted to avoid copying), for this to work. Converting S* to ref S (without copying) is an interesting idea for D. I wonder what those close to the definition of D and the compiler think of it.
It seems that the following discussion is relevant to the above. "Why can't we have reference variables" http://forum.dlang.org/thread/ruwapnhkuvozitefzplt forum.dlang.org A conservative viewpoint is that converting S* to ref S at the point of call requires that the pointer is valid (because non-pointer variables always work properly as a part of the way the language is defined), and there's no way that the compiler can simply verify that this is so. Therefore, such a conversion should be the programmers responsibility and not be done implicitly. Carl.
Sep 03 2012
parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Monday, 3 September 2012 at 15:46:49 UTC, Carl Sturtivant 
wrote:
 It seems that the following discussion is relevant to the above.

 "Why can't we have reference variables"
 http://forum.dlang.org/thread/ruwapnhkuvozitefzplt forum.dlang.org

 A conservative viewpoint is that converting S* to ref S at the 
 point of call requires that the pointer is valid (because 
 non-pointer variables always work properly as a part of the way 
 the language is defined), and there's no way that the compiler 
 can simply verify that this is so. Therefore, such a conversion 
 should be the programmers responsibility and not be done 
 implicitly.

 Carl.
Truth be told, I had thought of that argument, yet at the same time, the argument also applies for "s.foo();" What is bothering me is the inconsistent treatment. That and "there is no -> in D because it is not needed", when well, it sure feels like it is.
Sep 03 2012
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Monday, 3 September 2012 at 12:12:46 UTC, monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I 
 wanted it allocated on the heap. This meant I was now 
 manipulating S* instead of an S.
I answered this question in BZ couple of days ago in an issue 8603 which was filed after discussion about references in NG. Short answer: the equivalence of accessing members through pointers and values doesn't lead to equivalence of pointers and values in function parameters and one of possible problems is implicit dereference of pointers.
Sep 03 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, September 03, 2012 14:13:10 monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I
 wanted it allocated on the heap. This meant I was now
 manipulating S* instead of an S.
 
 I thought "this should have zero impact, because in D, because
 "." is supposed to deference for you if needed."
 
 I find it strange though that when trying to call a function with
 a pointer, the pointer isn't automatically dereferenced if needed:
 
 ----
 struct S
 {
    void foo();
 }
 void bar(S);
 void main()
 {
    auto r = new S;
    r.foo();
    bar(r);  //derp
    r.bar(); //derp
 };
 ----
 I find it strange, because I thought the entire point was to
 abstract way something was allocated to the way it was used: EG.
  From a caller perspective, I don't care if r is on the stack or
 on the heap: I just want to call the method bar on the object in
 question.
 
 Why does one consider a "free standing" function more ambiguous
 than a member function?
 
 Things get even stranger if you mix in uniform call syntax.
 "r.foo()" works, but "r.bar()" doesn't?
 
 Am I really forced into:
 ----
 struct S
 {
    void foo();
 }
 void bar(S);
 void main()
 {
    auto r = new S;
    r.foo();
    bar(*r);    //Groan
    (*r).bar(); //Super Groan.
 };
 ----
 I feel as if I'm just back at square 1...
All that UFCS does is make it so that if the first parameter of a function is a given type, you can call that function on that type as if it were a member function. It's purely syntactic convenience. void bar(S) {} takes an S, not as S*, so I wouldn't expect UFCS to work with it and an S*. . dereferences a pointer when accessing a member function or variable, because it works quite nicely to have it work that way and generally negates the need for a second operator (->). It's certainly _not_ true that the automatic dereferencing with . allows you to forget that something is a pointer. An operation which _could_ be on the pointer (e.g ==) will operate on the pointer, forcing you to dereference it. It's just that adding -> on top of . is unnecessary and complicates the language. The choice to have . automatically dereference pointers when access members predates the idea of UFCS considerably, and I don't think that it was ever really considered how they two would interact. The automatic dereferencing of a pointer doesn't really have anything to do with UFCS except for similarites of syntax. It's simply that if a variable is a pointer, and it points to a type which has a member with the same name as what's on the right-hand side of the dot, then that member function gets called. And technically, it doesn't even dereference the pointer, because the member function takes a pointer (as the invisible this pointer). If there is no such member function, then free functions are checked to see if they take the variable's type as their first argument. If there's such a function with the right name and number of arguments, then it's used. For there to be any dereferencing involved would require special casing pointers, which doesn't currently happen. I think that the way that it currently works is completely consistent. It's a perfectly valid enhancement request to want void func(S s, int i) {...} to be be callable with S*, given that normally function calls on an S* don't require you to dereference anything. But it's not like what we have is broken. It's just that there's a corner case which forces you to deal with pointers specially (which isn't exactly new, because things like == and assignment already require you to treat pointer specially). So, feel free to create an enhancement request. You've found a corner case that I suspect was never fully though through, and Walter may very well think that the change is worth making. However, one thing to remember that complicates this a bit is that it's perfectly possible to declare a function which is overloaded with one function taking a pointer and one not. void func(S* s, int i) {...} void func(S s, int i) {...} in which case, there's an ambiguity, and I would then expect UFCS to _not_ compile when using S*, or you'd risk function call hijacking. That's not necessarily a big deal, but it _does_ complicate things a bit. - Jonathan M Davis
Sep 03 2012
next sibling parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Monday, 3 September 2012 at 18:45:42 UTC, Jonathan M Davis 
wrote:
 On Monday, September 03, 2012 14:13:10 monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I
 wanted it allocated on the heap. This meant I was now
 manipulating S* instead of an S.
 
 I thought "this should have zero impact, because in D, because
 "." is supposed to deference for you if needed."
 
 I find it strange though that when trying to call a function 
 with
 a pointer, the pointer isn't automatically dereferenced if 
 needed:
 
 ----
 struct S
 {
    void foo();
 }
 void bar(S);
 void main()
 {
    auto r = new S;
    r.foo();
    bar(r);  //derp
    r.bar(); //derp
 };
 ----
 I find it strange, because I thought the entire point was to
 abstract way something was allocated to the way it was used: 
 EG.
  From a caller perspective, I don't care if r is on the stack 
 or
 on the heap: I just want to call the method bar on the object 
 in
 question.
 
 Why does one consider a "free standing" function more ambiguous
 than a member function?
 
 Things get even stranger if you mix in uniform call syntax.
 "r.foo()" works, but "r.bar()" doesn't?
 
 Am I really forced into:
 ----
 struct S
 {
    void foo();
 }
 void bar(S);
 void main()
 {
    auto r = new S;
    r.foo();
    bar(*r);    //Groan
    (*r).bar(); //Super Groan.
 };
 ----
 I feel as if I'm just back at square 1...
All that UFCS does is make it so that if the first parameter of a function is a given type, you can call that function on that type as if it were a member function. It's purely syntactic convenience. void bar(S) {} takes an S, not as S*, so I wouldn't expect UFCS to work with it and an S*. . dereferences a pointer when accessing a member function or variable, because it works quite nicely to have it work that way and generally negates the need for a second operator (->). It's certainly _not_ true that the automatic dereferencing with . allows you to forget that something is a pointer. An operation which _could_ be on the pointer (e.g ==) will operate on the pointer, forcing you to dereference it. It's just that adding -> on top of . is unnecessary and complicates the language. The choice to have . automatically dereference pointers when access members predates the idea of UFCS considerably, and I don't think that it was ever really considered how they two would interact. The automatic dereferencing of a pointer doesn't really have anything to do with UFCS except for similarites of syntax. It's simply that if a variable is a pointer, and it points to a type which has a member with the same name as what's on the right-hand side of the dot, then that member function gets called. And technically, it doesn't even dereference the pointer, because the member function takes a pointer (as the invisible this pointer). If there is no such member function, then free functions are checked to see if they take the variable's type as their first argument. If there's such a function with the right name and number of arguments, then it's used. For there to be any dereferencing involved would require special casing pointers, which doesn't currently happen. I think that the way that it currently works is completely consistent. It's a perfectly valid enhancement request to want void func(S s, int i) {...} to be be callable with S*, given that normally function calls on an S* don't require you to dereference anything. But it's not like what we have is broken. It's just that there's a corner case which forces you to deal with pointers specially (which isn't exactly new, because things like == and assignment already require you to treat pointer specially). So, feel free to create an enhancement request. You've found a corner case that I suspect was never fully though through, and Walter may very well think that the change is worth making. However, one thing to remember that complicates this a bit is that it's perfectly possible to declare a function which is overloaded with one function taking a pointer and one not. void func(S* s, int i) {...} void func(S s, int i) {...} in which case, there's an ambiguity, and I would then expect UFCS to _not_ compile when using S*, or you'd risk function call hijacking. That's not necessarily a big deal, but it _does_ complicate things a bit. - Jonathan M Davis
TY for the reply, I'll consider asking for it. In the mean time, is there a way to access that variable with value semantics? I mean, in the scope where my pointer was declared, I *know* it is non null. In C++, I often did it with iterators: ---- for(it ...) { int& val = *it; //now, we can access it with value semantics through val. } ---- ?
Sep 03 2012
parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 3 September 2012 at 19:49:14 UTC, monarch_dodra wrote:
 On Monday, 3 September 2012 at 18:45:42 UTC, Jonathan M Davis 
 wrote:
 On Monday, September 03, 2012 14:13:10 monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I
 wanted it allocated on the heap. This meant I was now
 manipulating S* instead of an S.
 
 I thought "this should have zero impact, because in D, because
 "." is supposed to deference for you if needed."
 
 I find it strange though that when trying to call a function 
 with
 a pointer, the pointer isn't automatically dereferenced if 
 needed:
 
 ----
 struct S
 {
   void foo();
 }
 void bar(S);
 void main()
 {
   auto r = new S;
   r.foo();
   bar(r);  //derp
   r.bar(); //derp
 };
 ----
 I find it strange, because I thought the entire point was to
 abstract way something was allocated to the way it was used: 
 EG.
 From a caller perspective, I don't care if r is on the stack 
 or
 on the heap: I just want to call the method bar on the object 
 in
 question.
 
 Why does one consider a "free standing" function more 
 ambiguous
 than a member function?
 
 Things get even stranger if you mix in uniform call syntax.
 "r.foo()" works, but "r.bar()" doesn't?
 
 Am I really forced into:
 ----
 struct S
 {
   void foo();
 }
 void bar(S);
 void main()
 {
   auto r = new S;
   r.foo();
   bar(*r);    //Groan
   (*r).bar(); //Super Groan.
 };
 ----
 I feel as if I'm just back at square 1...
All that UFCS does is make it so that if the first parameter of a function is a given type, you can call that function on that type as if it were a member function. It's purely syntactic convenience. void bar(S) {} takes an S, not as S*, so I wouldn't expect UFCS to work with it and an S*. . dereferences a pointer when accessing a member function or variable, because it works quite nicely to have it work that way and generally negates the need for a second operator (->). It's certainly _not_ true that the automatic dereferencing with . allows you to forget that something is a pointer. An operation which _could_ be on the pointer (e.g ==) will operate on the pointer, forcing you to dereference it. It's just that adding -> on top of . is unnecessary and complicates the language. The choice to have . automatically dereference pointers when access members predates the idea of UFCS considerably, and I don't think that it was ever really considered how they two would interact. The automatic dereferencing of a pointer doesn't really have anything to do with UFCS except for similarites of syntax. It's simply that if a variable is a pointer, and it points to a type which has a member with the same name as what's on the right-hand side of the dot, then that member function gets called. And technically, it doesn't even dereference the pointer, because the member function takes a pointer (as the invisible this pointer). If there is no such member function, then free functions are checked to see if they take the variable's type as their first argument. If there's such a function with the right name and number of arguments, then it's used. For there to be any dereferencing involved would require special casing pointers, which doesn't currently happen. I think that the way that it currently works is completely consistent. It's a perfectly valid enhancement request to want void func(S s, int i) {...} to be be callable with S*, given that normally function calls on an S* don't require you to dereference anything. But it's not like what we have is broken. It's just that there's a corner case which forces you to deal with pointers specially (which isn't exactly new, because things like == and assignment already require you to treat pointer specially). So, feel free to create an enhancement request. You've found a corner case that I suspect was never fully though through, and Walter may very well think that the change is worth making. However, one thing to remember that complicates this a bit is that it's perfectly possible to declare a function which is overloaded with one function taking a pointer and one not. void func(S* s, int i) {...} void func(S s, int i) {...} in which case, there's an ambiguity, and I would then expect UFCS to _not_ compile when using S*, or you'd risk function call hijacking. That's not necessarily a big deal, but it _does_ complicate things a bit. - Jonathan M Davis
TY for the reply, I'll consider asking for it. In the mean time, is there a way to access that variable with value semantics? I mean, in the scope where my pointer was declared, I *know* it is non null. In C++, I often did it with iterators: ---- for(it ...) { int& val = *it; //now, we can access it with value semantics through val. } ---- ?
Good question, I've proceeded in the same way in C++ many times. I hope there's a way, but I think it likely there is not, except by passing *it to a function with a reference parameter.
Sep 03 2012
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
 In C++, I often did it with iterators:
 ----
 for(it ...)
 {
     int& val = *it;
     //now, we can access it with value semantics through val.
 }
 ----

 ?
with(it) should help a lot but here. Still it was bogus back then when I used it with pointers, not sure how good it is now.
 Good question, I've proceeded in the same way in C++ many times. I hope
 there's a way, but I think it likely there is not, except by passing *it
 to a function with a reference parameter.
-- Olshansky Dmitry
Sep 03 2012
prev sibling next sibling parent "Carl Sturtivant" <sturtivant gmail.com> writes:
Just to be pellucidly clear, the case you think likely has merit 
is for an enhancement so that
  S* p; //suitably initialized
can e.g. make the call
  p.func(3);
of
  void func(S s, int i) { ... }
or
  void func(ref S s, int i) { ... }
right?
(Where it's important that the S parameter is first in the usual 
way, and the overloading rules are suitably amended to give this 
interpretation suitably low priority.)

Whereas you do not (correct me if I'm wrong) think that an 
implicit conversion of S* to ref S (or S) on function call is a 
good idea, e.g.
  S* p; //suitably initialized
cannot e.g. make the call
  func(p, 3);
of
  void func(S ref s, int i) { ... }
or
  void func(S s, int i) { ... }

So you've 'solved' one of the two calls that monarch_dodra 
indicated concern about, but not the other, which you think 
should require explicit indirection.

Assuming I've summarized the pragmatics of your post correctly 
(apologies otherwise), what is the reason for the non-uniformity 
here? As monarch_dodra points out, the dangers are the same in 
both calls. So if D is OK with one, why not with the other, which 
presents the possibility of nice syntactic simplification.

I guess this is tantamount to asking you why implicit conversion 
of S* to ref S would be so bad in general if you'll effectively 
permit that in certain cases (the call p.func(3) amounts to 
exactly that).

It'd be nice to hear the D insider view on this.

Carl.
Sep 03 2012
prev sibling parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 3 September 2012 at 18:45:42 UTC, Jonathan M Davis 
wrote:
 However, one thing to remember that complicates this a bit is 
 that it's perfectly possible to declare a function which is 
 overloaded with one function taking a pointer and one not.

 void func(S* s, int i) {...}
 void func(S s, int i) {...}

 in which case, there's an ambiguity, and I would then expect 
 UFCS to _not_ compile when using S*, or you'd risk function 
 call hijacking. That's not necessarily a big deal, but it 
 _does_ complicate things a bit.

 - Jonathan M Davis
I think moreso is would if it would convert to ref automatically or not rather than worry about pointers. True if you wanted all three, then the language has to keep them all distinctly different; But if it silently converts it should be okay (so long as constness/immutable is honored). Assuming that's the case: //these two effectively identical void func(S* s, int i) {...} void func(ref S s, int i) {...} I can see allowing a pointer be silently converted to a reference safely, however a normal to a pointer not so easily, not so much for type safety but accidents could be prone to happen. So... void funcP(S* s) {...} void funcR(ref S s) {...} struct S{} S *sp; //pointer S ss; //stack funcP(sp); //obviously will work fine funcP(ss); //error, pointer conversion may be confused for heap allocation funcR(sp); /*silently converting is acceptable as long as const/immutable is honored */ funcR(ss); //normal use as is used now. The reason it could be allowed as ref and not a pointer is the same as the argument for if you can have ref variables. If it's part of the function calling signature, you're guaranteed it's a live/valid item being passed, but a pointer would be required if it was a member item. However silently converting may have the result where functions using ref may suddenly have new issues if you can have a null reference (not normally possible), and it's impossible to check. S *p; //we know it's null funcR(p); //silently converts void funcR(ref S s) in{ //can't check for a null reference! It's not a pointer! } body { //time bomb with odd error when used? } Currently if I have it right, it would break if the object was invalid (during dereference/copying); You would have a more accurate error message that way (I'm certain). As a workaround you can have both functions and one call/convert to the other for you. With the workaround being this simple/small it's a very small price to pay; I would say automatically converting a pointer to a reference is likely something to be discussed; But I honestly would be against it for safety reasons. //maybe a ref or const versions as appropriate. void func(S s, int i) {...} //pointers should be the same (in my code) so here's my workaround! void func(S *s, int i) { func(*s, i); }
Sep 03 2012
parent "Regan Heath" <regan netmail.co.nz> writes:
On Mon, 03 Sep 2012 21:04:56 +0100, Era Scarecrow <rtcvb32 yahoo.com>  
wrote:

 On Monday, 3 September 2012 at 18:45:42 UTC, Jonathan M Davis wrote:
 However, one thing to remember that complicates this a bit is that it's  
 perfectly possible to declare a function which is overloaded with one  
 function taking a pointer and one not.

 void func(S* s, int i) {...}
 void func(S s, int i) {...}

 in which case, there's an ambiguity, and I would then expect UFCS to  
 _not_ compile when using S*, or you'd risk function call hijacking.  
 That's not necessarily a big deal, but it _does_ complicate things a  
 bit.

 - Jonathan M Davis
I think moreso is would if it would convert to ref automatically or not rather than worry about pointers. True if you wanted all three, then the language has to keep them all distinctly different; But if it silently converts it should be okay (so long as constness/immutable is honored). Assuming that's the case: //these two effectively identical void func(S* s, int i) {...} void func(ref S s, int i) {...}
What if the first function is in library A and the 2nd function in library B and they do two totally different things? R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Sep 04 2012
prev sibling next sibling parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 3 September 2012 at 12:12:46 UTC, monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I 
 wanted it allocated on the heap. This meant I was now 
 manipulating S* instead of an S.

 I thought "this should have zero impact, because in D, because 
 "." is supposed to deference for you if needed."

 I find it strange though that when trying to call a function 
 with a pointer, the pointer isn't automatically dereferenced if 
 needed:

 ----
 struct S
 {
   void foo();
 }
 void bar(S);
 void main()
 {
   auto r = new S;
   r.foo();
   bar(r);  //derp
   r.bar(); //derp
 };
 ----
 I find it strange, because I thought the entire point was to 
 abstract way something was allocated to the way it was used: 
 EG. From a caller perspective, I don't care if r is on the 
 stack or on the heap: I just want to call the method bar on the 
 object in question.

 Why does one consider a "free standing" function more ambiguous 
 than a member function?

 Things get even stranger if you mix in uniform call syntax. 
 "r.foo()" works, but "r.bar()" doesn't?

 Am I really forced into:
 ----
 struct S
 {
   void foo();
 }
 void bar(S);
 void main()
 {
   auto r = new S;
   r.foo();
   bar(*r);    //Groan
   (*r).bar(); //Super Groan.
 };
 ----
 I feel as if I'm just back at square 1...
In a nutshell, I think you're broadly saying that you want to program with a struct S the same way whether it's stack or heap allocated. (Good code reuse, and no duplication of semantics with -> as in C++.) From this perspective the trouble is that "S()" and "new S()" don't have the same effect except for allocating one on the stack and one on the heap, and the language forbids you from overcoming this via reference variables, except by calling a function and passing "*r" to a "ref S" parameter. So maybe that's what you should do with D in its present state. Once you've passed *r to the real main function for working with your big struct as "ref S", well, after that in all calls it's in value form. It's useful that "new S()" produces a pointer when building low level data structures in D, and your purpose is to heap-rather-than-stack allocate and proceed as before. These are completely different purposes. So I'm wondering if a language extension along the following lines would solve your problem, simply asking the compiler to use heap allocation when the variable is declared, e.g. heap auto s = S(); //secretly allocated with new, but used as if local where the compiler would know that s is valid, just as in a normal declaration. And the compiler would automatically deallocate the variable's heap storage when it goes out of scope. Otherwise the variable would behave like a normal local variable as far as inference of any kind made by the compiler in a wider context goes.
Sep 03 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, September 03, 2012 23:19:07 Carl Sturtivant wrote:
 So I'm wondering if a language extension along the following
 lines would solve your problem,
Whoa. Stop right there. Language extension? If you think that you need a language extension to solve a problem, odds are that you're going about things the wrong way. Sometimes a language extension would indeed be needed to do something, but odds are, it's not. D is a very powerful language. Think long and hard about how to do something _within_ the language before you even consider extending it. If all you're looking to do is make it so that you don't have to care whether you're dealing with an S or S*, then a language extension is complete overkill. Just make a wrapper struct. Something like (untested): struct Wrapper(T) { T* ptr; ref inout(T) get() inout { return *ptr; } alias get this; } Just wrap an S* in that, and you can use it exactly as if it were an S. Problem solved. - Jonathan M Davis
Sep 03 2012
parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 3 September 2012 at 21:44:15 UTC, Jonathan M Davis
wrote:
 On Monday, September 03, 2012 23:19:07 Carl Sturtivant wrote:
 So I'm wondering if a language extension along the following
 lines would solve your problem,
Whoa. Stop right there. Language extension? If you think that you need a language extension to solve a problem, odds are that you're going about things the wrong way. Sometimes a language extension would indeed be needed to do something, but odds are, it's not. D is a very powerful language. Think long and hard about how to do something _within_ the language before you even consider extending it. If all you're looking to do is make it so that you don't have to care whether you're dealing with an S or S*, then a language extension is complete overkill. Just make a wrapper struct. Something like (untested): struct Wrapper(T) { T* ptr; ref inout(T) get() inout { return *ptr; } alias get this; } Just wrap an S* in that, and you can use it exactly as if it were an S. Problem solved. - Jonathan M Davis
---same thing (specialized to struct S) in my first reply. I'm just fishing, seeking a boundary. There is a cleanness issue to discuss. It would be nice to know what you think about implicit conversion from 'S*' to 'ref S' (and not the other way as someone seemed to be suggesting). It's hard for a newish outsider to get a sense of where the boundaries lie.
Sep 03 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, September 04, 2012 00:21:53 Carl Sturtivant wrote:
 ---same thing (specialized to struct S) in my first reply.
 I'm just fishing, seeking a boundary.
 
 There is a cleanness issue to discuss. It would be nice to know
 what you think about implicit conversion from 'S*' to 'ref S'
 (and not the other way as someone seemed to be suggesting).
Pointers and ref are two entirely different things and shouldn't be treated the same. If you want to pass the pointer to ref S, then dereference it. Any and all implicit conversions should be treated with care. D has far fewer than C/C++ specifically because they're a major source of bugs.
 It's hard for a newish outsider to get a sense of where the boundaries
 lie.
We're trying _not_ to add new features. D is supposed to be stabilizing. The language definition is essentially frozen. We pretty much only add features when we really need to. Any and all new language features need to solve a real problem and need to carry their weight. If anything, the trend is to _remove_ features from the language and move them into the library rather than add them to the language (e.g. scope on local variables being axed in favor of std.typecons.Scoped). You need a _really_ good reason for a new language feature, or it's not going to happen. Pretty much the only feature that was added at all recently which had anything to do with syntactic niceties was UFCS, and that was extension of an existing feature with arrays, long sought-after, and depending on how you read TDPL, it implied that the feature was in the language. So, it got added. But in general, something that's trying to adjust syntax isn't going to happen, because that's a matter of aesthetics. _Always_ look to solve the problem within the language first. If you can, then it's _highly_ unlikely that your problem merits a new language feature. - Jonathan M Davis
Sep 03 2012
parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 3 September 2012 at 22:38:28 UTC, Jonathan M Davis 
wrote:
 On Tuesday, September 04, 2012 00:21:53 Carl Sturtivant wrote:
 ---same thing (specialized to struct S) in my first reply.
 I'm just fishing, seeking a boundary.
 
 There is a cleanness issue to discuss. It would be nice to know
 what you think about implicit conversion from 'S*' to 'ref S'
 (and not the other way as someone seemed to be suggesting).
Pointers and ref are two entirely different things and shouldn't be treated the same. If you want to pass the pointer to ref S, then dereference it. Any and all implicit conversions should be treated with care. D has far fewer than C/C++ specifically because they're a major source of bugs.
 It's hard for a newish outsider to get a sense of where the 
 boundaries
 lie.
We're trying _not_ to add new features. D is supposed to be stabilizing. The language definition is essentially frozen. We pretty much only add features when we really need to. Any and all new language features need to solve a real problem and need to carry their weight. If anything, the trend is to _remove_ features from the language and move them into the library rather than add them to the language (e.g. scope on local variables being axed in favor of std.typecons.Scoped). You need a _really_ good reason for a new language feature, or it's not going to happen. Pretty much the only feature that was added at all recently which had anything to do with syntactic niceties was UFCS, and that was extension of an existing feature with arrays, long sought-after, and depending on how you read TDPL, it implied that the feature was in the language. So, it got added. But in general, something that's trying to adjust syntax isn't going to happen, because that's a matter of aesthetics. _Always_ look to solve the problem within the language first. If you can, then it's _highly_ unlikely that your problem merits a new language feature. - Jonathan M Davis
Thank you for your frankness. The above being the case, I wonder why p.frog where p is a pointer has been made to work when a wrapper will do the job. Is that something that might best be removed? It is after all an aesthetic matter.
Sep 03 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, September 04, 2012 00:49:48 Carl Sturtivant wrote:
 Thank you for your frankness. The above being the case, I wonder
 why p.frog where p is a pointer has been made to work when a
 wrapper will do the job. Is that something that might best be
 removed? It is after all an aesthetic matter.
You mean getting rid of the automatic pointer dereferencing, making you do (*p).frog instead of p.frog? Well, regardless of the merits of one or the other, changing it would break code. The decisions for that were made ages ago. It doesn't merit revisiting at this point. And it's not that aesthetics don't matter. It's that they matter very little in comparison to other things, and given that changing them breaks code and given where we are in D's development, making such changes doesn't generally make sense. They needed to have been decided when the language was much younger and still in flux. As it is, it's mostly frozen, and we're looking to achieve full stability so that people can count on their D code continuing to compile. Changes which break code need a _really_ good reason to be made, and even if a change doesn't break code, we're not looking to complicate the language further if we don't need to. Also, the more things that get added, the less stable the compiler will be (at least in the short term) and the less work that gets put into fixing what we already have. D is past the point where we're looking to actively work on and change its design. We want it to be stable and useable like you get with mainstream languages like C++ or Java (only with the benefits of D's features of course). Adding more features (even if they're backwards compatible) distracts from that, and making breaking feature changes goes completely against it. I'm sure that once the compiler is more stable and all of the features that we already have work appropriately that we'll be more open to adding backwards compatible features (e.g. user-defined attributes). But this is just about the worst time in D's development to ask for features, because we're past the point of freezing the initial design but not yet stable enough to seriously consider adding additional, backwards-compatible features. So, new feature requests really need to pull their weight. - Jonathan M Davis
Sep 03 2012
parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 3 September 2012 at 23:13:26 UTC, Jonathan M Davis
wrote:
 On Tuesday, September 04, 2012 00:49:48 Carl Sturtivant wrote:
 Thank you for your frankness. The above being the case, I 
 wonder
 why p.frog where p is a pointer has been made to work when a
 wrapper will do the job. Is that something that might best be
 removed? It is after all an aesthetic matter.
You mean getting rid of the automatic pointer dereferencing, making you do (*p).frog instead of p.frog? Well, regardless of the merits of one or the other, changing it would break code. The decisions for that were made ages ago. It doesn't merit revisiting at this point. And it's not that aesthetics don't matter. It's that they matter very little in comparison to other things, and given that changing them breaks code and given where we are in D's development, making such changes doesn't generally make sense. They needed to have been decided when the language was much younger and still in flux. As it is, it's mostly frozen, and we're looking to achieve full stability so that people can count on their D code continuing to compile. Changes which break code need a _really_ good reason to be made, and even if a change doesn't break code, we're not looking to complicate the language further if we don't need to. Also, the more things that get added, the less stable the compiler will be (at least in the short term) and the less work that gets put into fixing what we already have. D is past the point where we're looking to actively work on and change its design. We want it to be stable and useable like you get with mainstream languages like C++ or Java (only with the benefits of D's features of course). Adding more features (even if they're backwards compatible) distracts from that, and making breaking feature changes goes completely against it. I'm sure that once the compiler is more stable and all of the features that we already have work appropriately that we'll be more open to adding backwards compatible features (e.g. user-defined attributes). But this is just about the worst time in D's development to ask for features, because we're past the point of freezing the initial design but not yet stable enough to seriously consider adding additional, backwards-compatible features. So, new feature requests really need to pull their weight. - Jonathan M Davis
Understood, and again thanks for speaking overtly and frankly about this. So in practical terms for this thread's issue, only the corner case is relevant at this point, of whether '.' as a courtesy might compile a call of bar(S s, int i) when p.bar(3) is written and p is of type S* --- and if the usual struct pointer courtesy for '.' is definitely in the language, then having this as well does seem more uniform. I hope it goes through. (Plus it helps to have somewhat reasonable explanations of what can and cannot be done to supply to students of programming: uniform rules help.)
Sep 03 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Monday, 3 September 2012 at 21:18:28 UTC, Carl Sturtivant 
wrote:
 On Monday, 3 September 2012 at 12:12:46 UTC, monarch_dodra 
 wrote:
 [SNIP]
In a nutshell, I think you're broadly saying that you want to program with a struct S the same way whether it's stack or heap allocated. (Good code reuse, and no duplication of semantics with -> as in C++.) From this perspective the trouble is that "S()" and "new S()" don't have the same effect except for allocating one on the stack and one on the heap, and the language forbids you from overcoming this via reference variables, except by calling a function and passing "*r" to a "ref S" parameter. [SNIP]
Yeah, in a nut shell, that is pretty much it. There are several ways to "work around it", but, IMO, none are good enough: *Function with ref: Too intrusive, especially for more complicated functions. *Structs that implicitly alias This (such as RefCounted): Ref Counted itself I'm not a huge fan of, since I don't see why I'd pay for RAII when I have a GC. As for the rest, it is not in the library, so I wouldn't want to roll one out myself. **Furthermore, these wrapper structs have a way of "tainting" the type system: When you pass your struct to a template, the template will instantiate on your struct itself, and not on the wrapped type. *structs with explicit dereference (those that have "get", for instance): That's just trading "*" for "get". For now, I'll just (*s) it. It isn't broken or anything... ---- What I regret though, is that since D is Garbage Collected, it is just screaming to be able to write: ---- S& val = *(new S); val.doSomthing(); doSomething(s); ... ---- This is legal in C++, but it leaks* :/ D should be able to harness such expressiveness with no problems whatsoever though. *Actually, I've done this in C++ for classes that have hefty attributes that need to be allocated, but otherwise don't need pointer functionalities. You just have to make sure to correctly implement the CC to avoid aliasing, and to "delete &val;" in the destructor. Once you've done this though, then for all intents and purposes, "val" is a value attribute. Nifty.
Sep 04 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 09/03/12 23:19, Carl Sturtivant wrote:
 So I'm wondering if a language extension along the following lines would solve
your problem, simply asking the compiler to use heap allocation when the
variable is declared, e.g.
 
   heap auto s = S(); //secretly allocated with new, but used as if local
 
 where the compiler would know that s is valid, just as in a normal
declaration. And the compiler would automatically deallocate the variable's
heap storage when it goes out of scope. Otherwise the variable would behave
like a normal local variable as far as inference of any kind made by the
compiler in a wider context goes.
It could be part of the type (annotating the object/instance isn't really any better than just using 'new'...), so struct S { new this(/*...*/) {/*...*/} /*...*/ } would let you skip the 'new' keyword. Might be useful when used with templates (and a 'scope this(){}' ctor could ensure stack allocation). But as right now struct allocations aren't properly supported yet (no custom allocators) adding something like that shouldn't have high priority, even if it can be done w/o breaking existing code (which does not use this feature). artur
Sep 04 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, September 03, 2012 14:13:10 monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I
 wanted it allocated on the heap. This meant I was now
 manipulating S* instead of an S.
[snip] Enhancement Request: http://d.puremagic.com/issues/show_bug.cgi?id=8616 - Jonathan M Davis
Sep 03 2012
parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 4 September 2012 at 02:42:42 UTC, Jonathan M Davis
wrote:
 On Monday, September 03, 2012 14:13:10 monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I
 wanted it allocated on the heap. This meant I was now
 manipulating S* instead of an S.
[snip] Enhancement Request: http://d.puremagic.com/issues/show_bug.cgi?id=8616 - Jonathan M Davis
That is a very well worded ER. Thank you.
Sep 04 2012
prev sibling next sibling parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 09/03/12 20:45, Jonathan M Davis wrote:
 It's a  perfectly valid enhancement request to want
 
 void func(S s, int i) {...}
 
 to be be callable with S*, given that normally function calls on an S* don't 
 require you to dereference anything.
No, it's not. See http://d.puremagic.com/issues/show_bug.cgi?id=8490 for why this would be a very bad idea. However 'void func(ref S s, ...){}' should (be made to) work, at least for the UFCS case - if it already doesn't (old compiler here...) - if 'func' is supposed to emulate a method then it should behave like one. artur
Sep 04 2012
parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Tuesday, 4 September 2012 at 10:51:36 UTC, Artur Skawina wrote:
 On 09/03/12 20:45, Jonathan M Davis wrote:
 It's a  perfectly valid enhancement request to want
 
 void func(S s, int i) {...}
 
 to be be callable with S*, given that normally function calls 
 on an S* don't require you to dereference anything.
No, it's not. See http://d.puremagic.com/issues/show_bug.cgi?id=8490 for why
this would be a very bad idea. However 'void func(ref S s, ...){}' should (be made to) work, at least for the UFCS case - if it already doesn't (old compiler here...) - if 'func' is supposed to emulate a method then it should behave like one. Hmmm... And here i consider the opposite true. With ref as it works, you can only pass 'live' (absolutely guaranteed to be allocated) variables to work, you can't guarantee that with a normal pointer. On the other hand. Let's consider the two calls. struct S { int x, y; int gety(){ return y;} } int getx(S s) {return s.x;} S ss; S *sp = &ss; //without errors for now writeln(ss.gety()); writeln(sp.gety()); /*pointer but as it currently works, the same (technically) */ writeln(ss.getx()); writeln(sp.getx()); //currently breaks without dereferencing If getx is valid with a pointer, it will be more consistent (and doesn't change anything). Now let's void it. sp = null; writeln(sp.gety()); //null pointer, refers to calling site writeln(sp.getx()); /*null pointer, breaks during copying and should refer to calling site*/ This I think is totally acceptable since it would be the same error and the same reason. If we take ref and allow that, you may as well allow referencing variables (and not just in the function signature) struct S { //may as well allow during building of struct, //it's just as safe as the rest of the ref calls now. ref S prev; //defaults to null this (ref S s) {prev = s}; int x; } int getx(ref S s) {return s.x} //now breaks inside getx writeln(sp.getx()); writeln(getx(sp)); //might compile?; More likely it's no longer a named variable writeln((*sp).getx()); writeln(getx(*sp)); //might compile, but now it's old C/C++ pointers. Very risky? writeln(getx(sp[0])); I ask you, how do you check if it's a null pointer? &s? int getx(ref S s) //How does this make sense?? it looks wrong and is misleading in {assert(&s); } body {return s.x); } More importantly, if it's now a possibility do we have to start adding checks everywhere? int getx(S *s) in {assert(s); } //perfectly acceptable check, we know it's a pointer body {return s.x); }
Sep 04 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, September 04, 2012 12:51:53 Artur Skawina wrote:
 On 09/03/12 20:45, Jonathan M Davis wrote:
 It's a perfectly valid enhancement request to want
 
 void func(S s, int i) {...}
 
 to be be callable with S*, given that normally function calls on an S*
 don't require you to dereference anything.
No, it's not. See http://d.puremagic.com/issues/show_bug.cgi?id=8490 for why this would be a very bad idea.
I completely disagree with that assessment. You already get copies with UFCS all over the place. It's just that if you could use a function which took the struct as a value with a pointer to the struct with UFCS, then you'd get a copy whereas if it's taking it by pointer, you wouldn't.
 However 'void func(ref S s, ...){}' should
 (be made to) work, at least for the UFCS case - if it already doesn't (old
 compiler here...) 
I completely disagree with this as well. If using UFCS with an S* and a function which takes an S works, then it should work with a function which takes ref S, but pointers are _not_ the same as ref at all, and I completely disagree with anything which try and make pointers convert to ref in the general case. It only makes sense in this particular case, because of how calling member functions on pointers to struct works, and in that case, the ref is irrelevant IMHO. Having functions take ref is _annoying_, because you can't pass rvalues to them, and so ref should be used sparingly. I don't see any real difference between having a function which takes an S being used with UFCS and having that same function used with an S*. A copy occurs in both cases. It's just that with S*, it means that the compiler has to implicitly dereference it for you (as it already does when accessing the struct's members). Other than that, the semantics are identical.
 - if 'func' is supposed to emulate a method then it
 should behave like one.
Which is exactly the point of this enhancement request. If you call a member function on a struct pointer, you don't need to dereference anything or really care that it's a pointer, but with UFCS, all of a sudden you do, which breaks the abstraction that UFCS is trying to provide. - Jonathan M Davis
Sep 04 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Monday, 3 September 2012 at 12:12:46 UTC, monarch_dodra wrote:
 I was playing around with a very big struct, and told myself I 
 wanted it allocated on the heap. This meant I was now 
 manipulating S* instead of an S.
I've been extensively trying things out since I first posted this. I'd like to give some feedback: ------------------------------------- First off, working with an S* has been a complete failure. The structure I was working with is several K. The problem is that come the first function call, my pointer is dereferenced and passed by value to the function, and then everything grinds to a halt. The approach works well in C++, because everything is passed by reference. D, on the other hand, which promotes *not* having copy constructors, also promotes pass-by-value, which is completely incompatible. I like D's approach, but it also means having to shift the way I design my patterns. ------------------------------------- The conclusion here is that the pointer must indeed be wrapped in some sort of structure, that has cheap copy. Things like RefCounted are actually *OK*, but as I was trying to write a "Reference" wrapper, I realized both have one *Major* flaw: Calling functions that return new objects, such as dup, save, opBinary etc... will leak the new object out of the "ReferenceType Wrapper" :/ On the other hand, I took my original S structure, and re-built it with an internal "Payload", and gave it reference semantics. *THAT* worked like a *CHARM* !!! ------------------------------------- Regarding the first Enhancement Request, I know think it is a "Bad Idea ®" : Not because of any null pointer problems (IMO, that is actually a non-issue), but because of the implicit cast from S* to S. The thing with member functions is that they _Truly_Do_ take a pointer. The operator "." is not actually a "convenience dereference". When you think about it, it is actually the [(*p).member|p->member] syntax which is strange (All the way back to C, yes): why reference an object, if behind the scenes, all you do is pass the address? I mean: struct S { void foo(); } void bar(S* p); void main() { S* p = new S; p.foo(); p.bar(); S s; (&s).foo(); (&s).bar(); } When you think about, *that* makes a lot of sense (to me), and UFCS works correctly with it.
Sep 05 2012
prev sibling parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 09/04/12 20:03, Jonathan M Davis wrote:
 On Tuesday, September 04, 2012 12:51:53 Artur Skawina wrote:
 On 09/03/12 20:45, Jonathan M Davis wrote:
 It's a perfectly valid enhancement request to want

 void func(S s, int i) {...}

 to be be callable with S*, given that normally function calls on an S*
 don't require you to dereference anything.
No, it's not. See http://d.puremagic.com/issues/show_bug.cgi?id=8490 for why this would be a very bad idea.
I completely disagree with that assessment. You already get copies with UFCS all over the place.
The fact that something is already broken is not an argument for making things even worse. As often in D, UFCS was done w/o properly thinking things through; it should only work with free functions that take a ref argument. Yes, accepting a value-passed arg is not technically "broken", but it /is/ unsound. struct S { int i; } ref other_func_expecting_an_S_ref(ref S s) { return s; } int* whatever; auto f1(S s) { s.i = 42; } auto f2(S s) { whatever = &s.i; } auto f3(S s) { return other_func_expecting_an_S_ref(s); } are just a few types of bugs that will appear other and other again. If an UFCS "method" wants to work on a copy, it can create one (or just call another non-ref-arg-non-UFCS function). Allowing the above means those bugs are not even warned about by the compiler and much programmer time will be wasted looking for the one place where someone forgot to add a 'ref' keyword while writing or modifying the code (w/o realizing that 's' is a private copy and that the code have been working previously only by luck). And yes - enforcing the only-ref-arg rule would probably restrict UFCS a bit, in that things that can be chained right now might no longer be usable that way, but it's just a matter of fixing them, possibly by allowing annotations that explicitly enable the call-by-value semantics for the type and/or function. The defaults should however be safe. Not introducing unsound features is easier than later removing them.
 It's just that if you could use a function which took the 
 struct as a value with a pointer to the struct with UFCS, then you'd get a 
 copy whereas if it's taking it by pointer, you wouldn't.
S* s; auto f(S* sp) {/*...*/}; s.f(); behaves as expected from an external POV, so there is no problem. Mistakenly causing a copy is not likely, so implementation bugs are not an issue either. S* s; auto f(S s) {/*...*/}; s.f(); *would* cause problems both for the 'f()' implementer /and/ user.
 However 'void func(ref S s, ...){}' should
 (be made to) work, at least for the UFCS case - if it already doesn't (old
 compiler here...) 
I completely disagree with this as well. If using UFCS with an S* and a function which takes an S works, then it should work with a function which takes ref S, but pointers are _not_ the same as ref at all, and I completely disagree with anything which try and make pointers convert to ref in the general case. It only makes sense in this particular case, because of how calling member functions on pointers to struct works, and in that case, the ref is irrelevant IMHO. Having functions take ref is _annoying_, because you can't pass rvalues to them, and so ref should be used sparingly.
Umm, UFCS *is* about calling "member functions" - this is exactly why UFCS "methods" should be treated just like "normal" ones. Allowing pass-this-by-value is an /extension/, which brings more harm than good.
 I don't see any real difference between having a function which takes an S 
 being used with UFCS and having that same function used with an S*. A copy 
 occurs in both cases. It's just that with S*, it means that the compiler has 
People working with pointer-to-structs are not going to expect that S* s; // ... s.method(); makes a copy of '*s' and operates on that. It's not how methods normally work; it's way to easy for this to happen by mistake.
 to implicitly dereference it for you (as it already does when accessing the 
 struct's members). Other than that, the semantics are identical.
 
 - if 'func' is supposed to emulate a method then it
 should behave like one.
Which is exactly the point of this enhancement request. If you call a member function on a struct pointer, you don't need to dereference anything or really care that it's a pointer, but with UFCS, all of a sudden you do, which breaks the abstraction that UFCS is trying to provide.
If you call a member function on a struct pointer with UFCS all of a sudden you need to care if it's a free function or a real method, because in the first case the struct might be copied implicitly, and you get no help from the compiler to detect such issues. That is (would be) the problem. On 09/04/12 20:19, Era Scarecrow wrote:
  I ask you, how do you check if it's a null pointer? &s?
Yes, obviously. If you need to do that manually.
   int getx(ref S s)
   //How does this make sense?? it looks wrong and is misleading
   in {assert(&s); }
   body {return s.x); }
It looks correct and is perfectly obvious. But see below - you don't need to do this manually - the compiler does it for you when calling methods and could handle the UFCS case too.
  More importantly, if it's now a possibility do we have to start adding checks
everywhere?
 
   int getx(S *s)
   in {assert(s); } //perfectly acceptable check, we know it's a pointer
   body {return s.x); }
struct S { int i; auto f() { return i; } } int main() { S* s; return s.f(); } This program will assert at runtime (and (correctly) segfault in a release-build). The compiler inserts not-null-this checks for "normal" methods in non-release mode, and could also do that before invoking any UFCS "method". So you wouldn't need to check for '!!&this' yourself. The problem w/ these checks is that they can not be disabled per-type - which prevents certain valid uses. The compiler-inserted assertions fire also when the methods can deal with null-this themselves (happens eg. when dealing with 'C' APIs and libraries, when you want to keep the C part as unmodified as possible). But that is a different issue. artur
Sep 05 2012
next sibling parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Wednesday, 5 September 2012 at 11:01:50 UTC, Artur Skawina 
wrote:
 On 09/04/12 20:19, Era Scarecrow wrote:
  I ask you, how do you check if it's a null pointer? &s?
Yes, obviously. If you need to do that manually.
But you shouldn't have to.
   int getx(ref S s)
   //How does this make sense?? it looks wrong and is misleading
   in {assert(&s); }
   body {return s.x); }
It looks correct and is perfectly obvious. But see below - you don't need to do this manually - the compiler does it for you when calling methods and could handle the UFCS case too.
How I'm understanding references in D (And perhaps I'm repeating myself in two different topics) is they point to live variables (IE guaranteed pointers), and remains this way until you return from that function. This is entirely valid and simplifies things. Remember in D we want the language to 'do the right thing', but if you make references where it works to 'sometimes works' then it becomes a problem (pointers 'sometimes' work and are not safe, while ref is safe). Checking the address of a reference shouldn't be needed, since it should be dereferenced at where it was called at if need be (throwing it there). Any side effects that may rely on the validity of the 'pointer' (once the function ends) are compromised afterwards (I'm always assume you pass local variables for this logic, since it's likely 80% of the time). Would you REALLY want to mark every single function that uses ref as trusted? You're then blindly telling the compiler to 'shut up' so you can use the code and ignoring the checking; Or am I wrong? int getItem(string[] inp, string cmp) safe { //foreach not safe! //only works not safe or blindly adding trusted foreach(i, ref s; inp) { if (s == cmp) return i; } //still might not compile, if foreach calls opApply //(Even if it doesn't use the reference). foreach(i, s; inp) { } }
  More importantly, if it's now a possibility do we have to 
 start adding checks everywhere?
 
   int getx(S *s)
   in {assert(s); } //perfectly acceptable check, we know it's 
 a pointer
   body {return s.x); }
    struct S { int i; auto f() { return i; } }
    int main() {
       S* s;
       return s.f();
    }

 This program will assert at runtime (and (correctly) segfault 
 in a release-build). The compiler inserts not-null-this checks 
 for "normal" methods in non-release mode, and could also do 
 that before invoking any UFCS "method". So you wouldn't need to 
 check for '!!&this' yourself.
I thought those checks weren't added (via the compiler) since if it causes a seg fault the CPU would catch it and kill the program on it's own (just add debugging flags); If you added the checks they would do the same thing (More buck for the same bang).
 The problem w/ these checks is that they can not be disabled 
 per-type - which prevents certain valid uses. The 
 compiler-inserted assertions fire also when the methods can 
 deal with null-this themselves (happens eg. when dealing with 
 'C' APIs and libraries, when you want to keep the C part as 
 unmodified as possible). But that is a different issue.
Sep 05 2012
next sibling parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 09/06/12 00:50, Era Scarecrow wrote:
 On Wednesday, 5 September 2012 at 11:01:50 UTC, Artur Skawina wrote:
 On 09/04/12 20:19, Era Scarecrow wrote:
  I ask you, how do you check if it's a null pointer? &s?
Yes, obviously. If you need to do that manually.
But you shouldn't have to.
   int getx(ref S s)
   //How does this make sense?? it looks wrong and is misleading
   in {assert(&s); }
   body {return s.x); }
It looks correct and is perfectly obvious. But see below - you don't need to do this manually - the compiler does it for you when calling methods and could handle the UFCS case too.
How I'm understanding references in D (And perhaps I'm repeating myself in two different topics) is they point to live variables (IE guaranteed pointers), and remains this way until you return from that function.
The struct example I gave previously (quoted below) shows how easily you can end up with a null reference in D; refs are *not* guaranteed to be live. It's not just about pointers: class C { int i; auto f() { return i; } } int main() { C c; return c.f(); } Here you won't even get an assert error, just a segfault. But the pointer-to-class (reference type in general, but so far there are only classes) model chosen in D is wrong; this probably contributes to the confusion about refs, because they behave differently for classes. Let's ignore classes for now, they're "special".
  This is entirely valid and simplifies things. Remember in D we want the
language to 'do the right thing', but if you make references where it works to
'sometimes works' then it becomes a problem (pointers 'sometimes' work and are
not  safe, while ref is  safe). Checking the address of a reference shouldn't
be needed, since it should be dereferenced at where it was called at if need be
(throwing it there).
Pointers *are* safe, it's just certain operations on them that are not.
  Would you REALLY want to mark every single function that uses ref as
 trusted? 
No idea why you think that would be needed.
  More importantly, if it's now a possibility do we have to start adding checks
everywhere?

   int getx(S *s)
   in {assert(s); } //perfectly acceptable check, we know it's a pointer
   body {return s.x); }
    struct S { int i; auto f() { return i; } }
    int main() {
       S* s;
       return s.f();
    }

 This program will assert at runtime (and (correctly) segfault in a
release-build). The compiler inserts not-null-this checks for "normal" methods
in non-release mode, and could also do that before invoking any UFCS "method".
So you wouldn't need to check for '!!&this' yourself.
I thought those checks weren't added (via the compiler) since if it causes a seg fault the CPU would catch it and kill the program on it's own (just add debugging flags); If you added the checks they would do the same thing (More buck for the same bang).
The checks happen for structs, and should be configurable, but right now are not, which sometimes causes trouble. On 09/06/12 08:18, Era Scarecrow wrote:
 On Wednesday, 5 September 2012 at 11:01:50 UTC, Artur Skawina wrote:
 On 09/04/12 20:19, Era Scarecrow wrote:
  I ask you, how do you check if it's a null pointer? &s?
Yes, obviously. If you need to do that manually.
   int getx(ref S s)
   //How does this make sense?? it looks wrong and is misleading
   in {assert(&s); }
   body {return s.x); }
It looks correct and is perfectly obvious. But see below - you don't need to do this manually - the compiler does it for you when calling methods and could handle the UFCS case too.
I've been thinking about this; It would definitely be the wrong thing to do. The assert would _Always_ succeed. The address you get would be of the pointer/reference for the stack (the pointer
No, that's not how ref args work. '&s' will give you the address of the object (eg struct). A reference type like 'class' has another (internal) level of indirection so in that case you would get a pointer to the class-reference. But that's how classes work internally, the 'object' in that case is just the internal pointer to the "real" class data. Taking the address of an argument gives you a pointer to it in every case. artur
Sep 06 2012
parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Thursday, 6 September 2012 at 10:29:17 UTC, Artur Skawina 
wrote:
 On 09/06/12 00:50, Era Scarecrow wrote:
 How I'm understanding references in D (And perhaps I'm 
 repeating myself in two different topics) is they point to 
 live variables (IE guaranteed pointers), and remains this way 
 until you return from that function.
The struct example I gave previously (quoted below) shows how easily you can end up with a null reference in D; refs are *not* guaranteed to be live. It's not just about pointers:
    class C { int i; auto f() { return i; } }
    int main() {
       C c;
       return c.f();
    }

 Here you won't even get an assert error, just a segfault. But 
 the pointer-to-class (reference type in general, but so far 
 there are only classes) model chosen in D is wrong; this 
 probably contributes to the confusion about refs, because they 
 behave differently for classes. Let's ignore classes for now, 
 they're "special".
Yeah, they are allocated, and 'can' still contain a null reference (or be deallocated/voided) in some way, so are pointers automatically.
 Pointers *are*  safe, it's just certain operations on them that 
 are not.
To my understanding I thought pointers (almost everything of them) was not covered in SafeD/ safe code. True as long as you don't mess with the pointer, than the object could remain valid (assuming it was allocated), but you automatically go into low-level code, and in trusted or system programing.
  Would you REALLY want to mark every single function that uses 
 ref as  trusted?
 No idea why you think that would be needed.
Because we aren't allocating everything on the heap. Maybe I'm just seeing things at a very different angle than you. Maybe I need a core dump for my head.
 I've been thinking about this; It would definitely be the 
 wrong thing to do. The assert would _Always_ succeed. The 
 address you get would be of the pointer/reference for the 
 stack (the pointer
No, that's not how ref args work. '&s' will give you the address of the object (eg struct). A reference type like 'class' has another (internal) level of indirection so in that case you would get a pointer to the class-reference. But that's how classes work internally, the 'object' in that case is just the internal pointer to the "real" class data. Taking the address of an argument gives you a pointer to it in every case.
Curious. Both ways could be correct. But somehow I don't think so... Alright let's go the opposite direction. Give me an example in which passing a variable (by reference to a function) would EVER require it to check the address to see if it was null. Class/allocated objects should fail before the function gets control. ie: void func(ref int i); class X { int i; } X x; int* i; int[10] a; func(x.i); /*should fail while dereferencing x to access i, so never gets to func*/ func(*i); //does this count as a lvalue? Probably not, func(a[0]);//none of these three should compile with that in mind func(0); Being named variables, and likely non-classes you are then left with mostly local variables, or arrays, or some type of pointer indirection issue. But ever case I come up with says it would fail before the function was called.
Sep 06 2012
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 09/06/12 13:34, Era Scarecrow wrote:
  Alright let's go the opposite direction. Give me an example in which passing
a variable (by reference to a function) would EVER require it to check the
address to see if it was null. Class/allocated objects should fail before the
function gets control. ie:
 
  void func(ref int i);
 
  class X {
    int i;
  }
 
  X x;
  int* i;
  int[10] a;
 
  func(x.i); /*should fail while dereferencing x to access i,
               so never gets to func*/
  func(*i);  //does this count as a lvalue? Probably not,
  func(a[0]);//none of these three should compile with that in mind
  func(0);
 
  Being named variables, and likely non-classes you are then left with mostly
local variables, or arrays, or some type of pointer indirection issue. But ever
case I come up with says it would fail before the function was called.
Both '*i' and 'a[0]' count. (Even '0' could be made to work as a 'const ref' arg, but i'm not sure if that would be a good idea) artur
Sep 06 2012
parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Thursday, 6 September 2012 at 12:00:05 UTC, Artur Skawina 
wrote:
 On 09/06/12 13:34, Era Scarecrow wrote:
 Alright let's go the opposite direction. Give me an example in 
 which passing a variable (by reference to a function) would 
 EVER require it to check the address to see if it was null. 
 Class/allocated objects should fail before the function gets 
 control. ie:
 
  void func(ref int i);
 
  class X {
    int i;
  }
 
  X x;
  int* i;
  int[10] a;
 
  func(x.i); /*should fail while dereferencing x to access i,
               so never gets to func*/
  func(*i);  //does this count as a lvalue? Probably not,
  func(a[0]);//none of these three should compile with that in 
 mind
  func(0);
 
 Being named variables, and likely non-classes you are then 
 left with mostly local variables, or arrays, or some type of 
 pointer indirection issue. But ever case I come up with says 
 it would fail before the function was called.
Both '*i' and 'a[0]' count. (Even '0' could be made to work as a 'const ref' arg, but i'm not sure if that would be a good idea)
I wasn't sure about *i. I can see it going either way. *i would need to be dereferenced first, a[0] would need a bounds check which then ensures it exists (even if it was dynamic); So checking an address from ref wouldn't be needed in the func.
Sep 06 2012
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 09/06/12 22:07, Era Scarecrow wrote:
 On Thursday, 6 September 2012 at 12:00:05 UTC, Artur Skawina wrote:
 On 09/06/12 13:34, Era Scarecrow wrote:
 Alright let's go the opposite direction. Give me an example in which passing a
variable (by reference to a function) would EVER require it to check the
address to see if it was null. Class/allocated objects should fail before the
function gets control. ie:

  void func(ref int i);

  class X {
    int i;
  }

  X x;
  int* i;
  int[10] a;

  func(x.i); /*should fail while dereferencing x to access i,
               so never gets to func*/
  func(*i);  //does this count as a lvalue? Probably not,
  func(a[0]);//none of these three should compile with that in mind
  func(0);

 Being named variables, and likely non-classes you are then left with mostly
local variables, or arrays, or some type of pointer indirection issue. But ever
case I come up with says it would fail before the function was called.
Both '*i' and 'a[0]' count. (Even '0' could be made to work as a 'const ref' arg, but i'm not sure if that would be a good idea)
I wasn't sure about *i. I can see it going either way. *i would need to be dereferenced first, a[0] would need a bounds check which then ensures it exists (even if it was dynamic); So checking an address from ref wouldn't be needed in the func.
No, '*i' does not actually dereference the pointer when used as a ref argument. This program won't assert auto func(ref int i) { assert(!&i); } void main() { int* i; func(*i); } and would segfault if 'i' was accessed by 'func'. artur
Sep 06 2012
parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Thursday, 6 September 2012 at 21:58:43 UTC, Artur Skawina 
wrote:
 This program won't assert
 and would segfault if 'i' was accessed by 'func'.
So it seems. The way I read it says it dereferences it first. Regardless that you are forcibly referencing a pointer which is low-level trickery; which I'm sure is undefined behavior. Course if you have to check/treat it as a pointer, adding safe suddenly refuses to compile (Won't let you get the address); But if you leave it out (without trusted either) and make main safe, suddenly that doesn't compile either. (Because I'm sure quite a few functions you'd want to share will end up being safe and pure). auto func(ref int i) safe { assert(!&i); //won't compile as safe debug { //ignore that writeln is system writeln("Address:", &i); //here too writeln("Value:", i); } return i; } void main() safe { int v = 100; func(v); int* i; func(*i); } As I commented before: Would you really want to blindly put trusted on everything? In order for the above to work, either neither is safe, or func is trusted. Course you can always leave safe out, assuming you aren't making anything you intend to share or will never be called by anything that's safe.
Sep 06 2012
next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 7 September 2012 at 00:51:42 UTC, Era Scarecrow wrote:
  So it seems. The way I read it says it dereferences it first. 
 Regardless that you are forcibly referencing a pointer which is 
 low-level trickery; which I'm sure is undefined behavior.
*Technically*, I think it is only undefined behavior once you *read* the dereferenced value... and passing by reference doesn't do that.
Sep 06 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 09/07/12 02:52, Era Scarecrow wrote:
 On Thursday, 6 September 2012 at 21:58:43 UTC, Artur Skawina wrote:
 This program won't assert
 and would segfault if 'i' was accessed by 'func'.
So it seems. The way I read it says it dereferences it first. Regardless that you are forcibly referencing a pointer which is low-level trickery; which I'm sure is undefined behavior.
There's nothing undefined about passing args by reference; i know the '*s' looks misleading, but what happens when such an expression is used as a ref arg is basically '&*s' and then the compiler will let you access the result via the function parameter; it's essentially syntax sugar (with a few extra usage restrictions).
  Course if you have to check/treat it as a pointer, adding  safe suddenly
refuses to compile (Won't let you get the address); But if you leave it out
(without  trusted either) and make main  safe, suddenly that doesn't compile
either. (Because I'm sure quite a few functions you'd want to share will end up
being  safe and pure).
'&ref_arg' not being allowed in safe mode is indeed a safe-problem. But it's not easily fixable right now, because scope enforcement isn't done - so it would be too easy to leak a reference (pointer). Until it works with safe, you can do safe: auto func(ref int i) { trusted check() { assert(!&i); } check(); /*rest of safe code*/ } void main() { int* i; func(*i); } which at least keeps the trusted part to a minimum. Yeah, it should be possible to do just ' trusted assert(!&i);', ie have trusted scopes, not just functions; see http://www.digitalmars.com/d/archives/digitalmars/D/trusted_considered harmful_173515.html . artur
Sep 07 2012
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 09/06/12 12:29, Artur Skawina wrote:
 that case is just the  internal pointer to the "real" class data. Taking
 the address of an argument gives you a pointer to it in every case.
...gives you a pointer to that argument... would have been be less ambiguous. artur
Sep 06 2012
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 09/06/12 12:29, Artur Skawina wrote:
    class C { int i; auto f() { return i; } }
    int main() {
       C c;
       return c.f();
    }
 
 Here you won't even get an assert error, just a segfault. But the
pointer-to-class
Argh. The reason you won't get get an assert is because I forgot to add 'final' when converting the struct example... class C { int i; final f() { return i; } } int main() { C c; return c.f(); } BTW, why doesn't GDC figure this out by itself? IIRC GCC gets these cases right both for C++ and C (!), but does not devirtualize D methods, not even in LTO/WPR mode... artur
Sep 06 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 6 September 2012 11:58, Artur Skawina <art.08.09 gmail.com> wrote:
 On 09/06/12 12:29, Artur Skawina wrote:
    class C { int i; auto f() { return i; } }
    int main() {
       C c;
       return c.f();
    }

 Here you won't even get an assert error, just a segfault. But the
pointer-to-class
Argh. The reason you won't get get an assert is because I forgot to add 'final' when converting the struct example... class C { int i; final f() { return i; } } int main() { C c; return c.f(); } BTW, why doesn't GDC figure this out by itself? IIRC GCC gets these cases right both for C++ and C (!), but does not devirtualize D methods, not even in LTO/WPR mode... artur
All methods are virtual by default in D. If you feel there is something that can be improved in GDC's codegen, please send a testcase and a written example of the behaviour it should show, and I will look into it. :-) Regards -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';
Sep 06 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 09/06/12 18:21, Iain Buclaw wrote:
 On 6 September 2012 11:58, Artur Skawina <art.08.09 gmail.com> wrote:
 On 09/06/12 12:29, Artur Skawina wrote:
    class C { int i; auto f() { return i; } }
    int main() {
       C c;
       return c.f();
    }

 Here you won't even get an assert error, just a segfault. But the
pointer-to-class
Argh. The reason you won't get get an assert is because I forgot to add 'final' when converting the struct example... class C { int i; final f() { return i; } } int main() { C c; return c.f(); } BTW, why doesn't GDC figure this out by itself? IIRC GCC gets these cases right both for C++ and C (!), but does not devirtualize D methods, not even in LTO/WPR mode...
All methods are virtual by default in D. If you feel there is something that can be improved in GDC's codegen, please send a testcase and a written example of the behaviour it should show, and I will look into it. :-)
I'm just wondering /why/ the optimization doesn't happen for D; there are far more important issues like cross-module inlining. Eventually devirtualization will be needed exactly because all methods are virtual. The test case would be something like the following two programs: C++: class C { public: virtual int foo(int i) { return i+1; } }; int main() { C* c = new C; int i = 0; i = c->foo(i); return i; } D: class C { int foo(int i) { return i+1; } }; int main() { C c = new C; int i = 0; i = c.foo(i); return i; } The first compiles to: 8048420: 83 ec 04 sub $0x4,%esp 8048423: c7 04 24 04 00 00 00 movl $0x4,(%esp) 804842a: e8 e1 ff ff ff call 8048410 <_Znwj plt> 804842f: c7 00 a0 85 04 08 movl $0x80485a0,(%eax) 8048435: b8 01 00 00 00 mov $0x1,%eax 804843a: 83 c4 04 add $0x4,%esp 804843d: c3 ret but the second results in: 8049820: 55 push %ebp 8049821: 89 e5 mov %esp,%ebp 8049823: 83 ec 18 sub $0x18,%esp 8049826: c7 04 24 c0 47 07 08 movl $0x80747c0,(%esp) 804982d: e8 0e 4d 00 00 call 804e540 <_d_newclass> 8049832: 8b 10 mov (%eax),%edx 8049834: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 804983b: 00 804983c: 89 04 24 mov %eax,(%esp) 804983f: ff 52 18 call *0x18(%edx) 8049842: c9 leave 8049843: c3 ret ie in the D version the call isn't devirtualized. artur
Sep 06 2012
prev sibling parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Wednesday, 5 September 2012 at 11:01:50 UTC, Artur Skawina 
wrote:
 On 09/04/12 20:19, Era Scarecrow wrote:
  I ask you, how do you check if it's a null pointer? &s?
Yes, obviously. If you need to do that manually.
   int getx(ref S s)
   //How does this make sense?? it looks wrong and is misleading
   in {assert(&s); }
   body {return s.x); }
It looks correct and is perfectly obvious. But see below - you don't need to do this manually - the compiler does it for you when calling methods and could handle the UFCS case too.
I've been thinking about this; It would definitely be the wrong thing to do. The assert would _Always_ succeed. The address you get would be of the pointer/reference for the stack (the pointer variable exists, where it points to isn't so much the case), so it would be the same as comparing it to this... int getx(S* s) in {assert(&s);} //always true, and always wrong. As I mentioned, it's wrong and is misleading. You'd have to work around the system to get the check correct; and even then if the compile decides to do something different you can only have it implementation dependent. int getx(ref S s) in { S *ptr = cast(S*) s; assert(ptr); } I'm not even sure this would even work (it's undefined afterall). I hope I never have to start adding such odd looking checks, else I would throw out ref and use pointers instead; At least with them the checks are straight-forward in comparison.
Sep 05 2012
parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Thursday, 6 September 2012 at 06:18:11 UTC, Era Scarecrow 
wrote:
 On Wednesday, 5 September 2012 at 11:01:50 UTC, Artur Skawina 
 wrote:
 On 09/04/12 20:19, Era Scarecrow wrote:
 I ask you, how do you check if it's a null pointer? &s?
Yes, obviously. If you need to do that manually.
  int getx(ref S s)
  //How does this make sense?? it looks wrong and is misleading
  in {assert(&s); }
  body {return s.x); }
It looks correct and is perfectly obvious. But see below - you don't need to do this manually - the compiler does it for you when calling methods and could handle the UFCS case too.
I've been thinking about this; It would definitely be the wrong thing to do. The assert would _Always_ succeed. The address you get would be of the pointer/reference for the stack (the pointer variable exists, where it points to isn't so much the case), so it would be the same as comparing it to this... int getx(S* s) in {assert(&s);} //always true, and always wrong.
That is absolutely not true at all. Behind the scenes, *passing* a ref is the same as passing a pointer, yes, but afterwards, they are different entities. If you request the address of the reference, it *will* give you the address of the referenced object. It is NOT the same as what you just wrote: -------- import std.stdio; struct S { int i; } void foo(ref S s) { writeln("address of s is: ", &s); assert(&s); } void main() { S* p; foo(*p); } -------- address of s is: null core.exception.AssertError main(11): Assertion failure --------
  As I mentioned, it's wrong and is misleading. You'd have to 
 work around the system to get the check correct; and even then 
 if the compile decides to do something different you can only 
 have it implementation dependent.

   int getx(ref S s)
   in {
     S *ptr = cast(S*) s;
     assert(ptr);
   }

  I'm not even sure this would even work (it's undefined 
 afterall). I hope I never have to start adding such odd looking 
 checks, else I would throw out ref and use pointers instead; At 
 least with them the checks are straight-forward in comparison.
Again, a reference and a pointer are not the same thing. That cast is illegal. -------- main.d(10): Error: e2ir: cannot cast s of type S to type S* -------- But *this* is legal and good though: -------- int getx(ref S s) in { S *ptr = &s; assert(ptr); } -------- Although it is just transforming the initial 1-liner into a 2-liner...
Sep 06 2012