Jump to page: 1 25  
Page
Thread overview
pointers, functions, and uniform call syntax
Sep 03, 2012
monarch_dodra
Sep 03, 2012
Carl Sturtivant
Sep 03, 2012
Carl Sturtivant
Sep 03, 2012
monarch_dodra
Sep 03, 2012
Maxim Fomin
Sep 03, 2012
Jonathan M Davis
Sep 03, 2012
monarch_dodra
Sep 03, 2012
Carl Sturtivant
Sep 03, 2012
Dmitry Olshansky
Sep 03, 2012
Carl Sturtivant
Sep 03, 2012
Era Scarecrow
Sep 04, 2012
Regan Heath
Sep 03, 2012
Carl Sturtivant
Sep 03, 2012
Jonathan M Davis
Sep 03, 2012
Carl Sturtivant
Sep 03, 2012
Jonathan M Davis
Sep 03, 2012
Carl Sturtivant
Sep 03, 2012
Jonathan M Davis
Sep 04, 2012
Carl Sturtivant
Sep 04, 2012
monarch_dodra
Sep 04, 2012
Artur Skawina
Sep 04, 2012
Jonathan M Davis
Sep 04, 2012
monarch_dodra
Sep 04, 2012
Artur Skawina
Sep 04, 2012
Era Scarecrow
Sep 04, 2012
Jonathan M Davis
Sep 05, 2012
monarch_dodra
Sep 05, 2012
Artur Skawina
Sep 05, 2012
Era Scarecrow
Sep 06, 2012
Artur Skawina
Sep 06, 2012
Era Scarecrow
Sep 06, 2012
Artur Skawina
Sep 06, 2012
Era Scarecrow
Sep 06, 2012
Artur Skawina
Sep 07, 2012
Era Scarecrow
Sep 07, 2012
monarch_dodra
Sep 07, 2012
Artur Skawina
Sep 06, 2012
Artur Skawina
Sep 06, 2012
Artur Skawina
Sep 06, 2012
Iain Buclaw
Sep 06, 2012
Artur Skawina
Sep 06, 2012
Era Scarecrow
Sep 06, 2012
monarch_dodra
September 03, 2012
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...
September 03, 2012
> 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.

#!/usr/bin/rdmd

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();
}


September 03, 2012
>> ----
>> 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.

September 03, 2012
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.
September 03, 2012
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.

September 03, 2012
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
September 03, 2012
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.
}
----

?
September 03, 2012
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.

September 03, 2012
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.


September 03, 2012
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);
  }
« First   ‹ Prev
1 2 3 4 5