www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - UFCS functions with both pointers and refs

reply Dave P. <dave287091 gmail.com> writes:
If I define a method on a type, then I can call it both through a 
pointer and
through a reference and the compiler does the right thing. Eg:

struct Foo {
     int x;
     void fooey(){
         x++;
     }
     void report(){
         printf("%d\n", x);
     }
}

int main(){
     Foo f;
     f.fooey;
     Foo* pf = &f;
     pf.fooey;
     f.report; // prints 2
     return 0;
}

However, if I define it as a free function and try to invoke it 
via UFCS,
it seems like I have to define it twice.

struct Foo {
     int x;
     void report(){
         printf("%d\n", x);
     }
}

void fooey(Foo* f){
     f.x++;
}
void fooey(ref Foo f){
     f.x++;
}

int main(){
     Foo f;
     f.fooey;
     Foo* pf = &f;
     pf.fooey;
     f.report; // prints 2
     return 0;
}

Am I missing something or is this just how it has to work 
generally?
Do I have to write both and have one forward to the other for more
complicated functions?
Dec 13 2020
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Sunday, 13 December 2020 at 18:31:54 UTC, Dave P. wrote:
 If I define a method on a type, then I can call it both through 
 a pointer and
 through a reference and the compiler does the right thing. Eg:

 struct Foo {
     int x;
     void fooey(){
         x++;
     }
     void report(){
         printf("%d\n", x);
     }
 }

 int main(){
     Foo f;
     f.fooey;
     Foo* pf = &f;
     pf.fooey;
     f.report; // prints 2
     return 0;
 }

 However, if I define it as a free function and try to invoke it 
 via UFCS,
 it seems like I have to define it twice.

 struct Foo {
     int x;
     void report(){
         printf("%d\n", x);
     }
 }

 void fooey(Foo* f){
     f.x++;
 }
 void fooey(ref Foo f){
     f.x++;
 }

 int main(){
     Foo f;
     f.fooey;
     Foo* pf = &f;
     pf.fooey;
     f.report; // prints 2
     return 0;
 }

 Am I missing something or is this just how it has to work 
 generally?
These are two very different concepts. Member functions have a hidden 'this' parameter as the first function parameter. For structs, it's a reference to the instance. Whether you call it through a pointer or a reference, that never changes: there's only one implementation of the function, the first parameter is always a reference to the instance. Free functions do not belong to any type (hence the "free"). UFCS doesn't change that. UFCS is simply a convenience that rewrites `foo.func` as `func(foo)`. You aren't calling "through" a pointer or a reference. So if the first parameter is a pointer, you can't give it a reference, and vice versa.
 Do I have to write both and have one forward to the other for 
 more
 complicated functions?
For free functions, yes.
Dec 13 2020
parent reply Dave P. <dave287091 gmail.com> writes:
On Sunday, 13 December 2020 at 18:44:20 UTC, Mike Parker wrote:
 On Sunday, 13 December 2020 at 18:31:54 UTC, Dave P. wrote:
 Do I have to write both and have one forward to the other for 
 more
 complicated functions?
For free functions, yes.
Is there any way to write the function as a template that is generic over a parameter being a pointer or a reference, but does not allow passing a copy?
Dec 13 2020
next sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Sunday, 13 December 2020 at 19:02:34 UTC, Dave P. wrote:
 On Sunday, 13 December 2020 at 18:44:20 UTC, Mike Parker wrote:
 On Sunday, 13 December 2020 at 18:31:54 UTC, Dave P. wrote:
 Do I have to write both and have one forward to the other for 
 more
 complicated functions?
For free functions, yes.
Is there any way to write the function as a template that is generic over a parameter being a pointer or a reference, but does not allow passing a copy?
I'm not sure what you mean by a reference. D doesn't have references in general, only class references that are just glorified pointers. There are also `ref` parameters, but those aren't generally referred to as "references" and are inside the function almost indiscernible from non-ref parameters. So, I'll ignore that. Copying only takes place under one circumstance: When an lvalue is passed to a function that does not take that argument by `ref`. So one possibility is to just define that overload and disable it. You don't even need a template for this: void f( X x); // matches lvalues and rvalues void f(ref X x); // matches lvalues only The latter is a better match than the former for lvalues. disable'ing it will do the job. On the other hand, not disable'ing it will make `f` work with any argument by moving rvalues to the former overload and referencing lvalues using the second one. On templates, those can be unified by slapping `auto ref` before the parameter. You can also use `auto ref` (which infers `ref` from the passed argument) and check it with an `if` template constraint: void f(T)(auto ref T arg) if (!__tratis(isRef, arg)) // only accepts non-ref args { /* your code here */ } The constraint can easily be flipped.
Dec 15 2020
parent reply Dave P. <dave287091 gmail.com> writes:
On Tuesday, 15 December 2020 at 19:45:50 UTC, Q. Schroll wrote:
 On Sunday, 13 December 2020 at 19:02:34 UTC, Dave P. wrote:
 On Sunday, 13 December 2020 at 18:44:20 UTC, Mike Parker wrote:
 On Sunday, 13 December 2020 at 18:31:54 UTC, Dave P. wrote:
 Do I have to write both and have one forward to the other 
 for more
 complicated functions?
For free functions, yes.
Is there any way to write the function as a template that is generic over a parameter being a pointer or a reference, but does not allow passing a copy?
I'm not sure what you mean by a reference. D doesn't have references in general, only class references that are just glorified pointers. There are also `ref` parameters, but those aren't generally referred to as "references" and are inside the function almost indiscernible from non-ref parameters. So, I'll ignore that. Copying only takes place under one circumstance: When an lvalue is passed to a function that does not take that argument by `ref`. So one possibility is to just define that overload and disable it. You don't even need a template for this: void f( X x); // matches lvalues and rvalues void f(ref X x); // matches lvalues only The latter is a better match than the former for lvalues. disable'ing it will do the job. On the other hand, not disable'ing it will make `f` work with any argument by moving rvalues to the former overload and referencing lvalues using the second one. On templates, those can be unified by slapping `auto ref` before the parameter. You can also use `auto ref` (which infers `ref` from the passed argument) and check it with an `if` template constraint: void f(T)(auto ref T arg) if (!__tratis(isRef, arg)) // only accepts non-ref args { /* your code here */ } The constraint can easily be flipped.
The use case would be to define extension methods on a struct outside of where the struct is defined. The extension method mutates the state of the struct, so I want to ensure I am modifying the original struct and not a copy. If it’s a method and I call it on a pointer to the struct, the pointer will get auto-dereferenced and everything is great. So my question is that if I want an extension method as a free function, do I have to write both the version whose first argument is a pointer to the struct and the version whose first argument is a ref, or is there some keyword or other technique so that the pointer gets auto-dereferenced the same way as if it were a method. It sounds like the answer is no and I have to write a version that just dereferences the pointer and calls the ref version. Thanks for the explanation though!
Dec 15 2020
parent Q. Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 15 December 2020 at 20:38:04 UTC, Dave P. wrote:
 The use case would be to define extension methods on a struct 
 outside of where the struct is defined. The extension method 
 mutates the state of the struct, so I want to ensure I am 
 modifying the original struct and not a copy. If it’s a method 
 and I call it on a pointer to the struct, the pointer will get 
 auto-dereferenced and everything is great. So my question is 
 that if I want an extension method as a free function, do I 
 have to write both the version whose first argument is a 
 pointer to the struct and the version whose first argument is a 
 ref, or is there some keyword or other technique so that the 
 pointer gets auto-dereferenced the same way as if it were a 
 method. It sounds like the answer is no and I have to write a 
 version that just dereferences the pointer and calls the ref 
 version.
Now it's coming together. The easiest way to do this is using an overload taking a `ref` (non-pointer) value and another taking a pointer (by copy, I guess). R f(ref T value, Ts args) { /* doing the actual work */ } R f(T* ptr, Ts args) { f(*p, args); } // manually dereference You cannot get around doing the actual `f`, and the addition isn't that big. I think that's the best solution unless you have to deal with non-copyable types among Ts. Then you need to forward those properly. I couldn't really do better using one template instead of this simple overload. (Certainly a template can be made, but it is clearly not the better solution.)
Dec 17 2020
prev sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Sunday, 13 December 2020 at 19:02:34 UTC, Dave P. wrote:
 On Sunday, 13 December 2020 at 18:44:20 UTC, Mike Parker wrote:
 On Sunday, 13 December 2020 at 18:31:54 UTC, Dave P. wrote:
 Do I have to write both and have one forward to the other for 
 more
 complicated functions?
For free functions, yes.
Is there any way to write the function as a template that is generic over a parameter being a pointer or a reference, but does not allow passing a copy?
Even with a templated function, you'd still need to declare the first parameter to the function either as a pointer or as a `ref` parameter. I'm unaware of any way to get around that. Free functions and member functions are just different beasts Based on you requirement to use pointers, I assume you're doing this for a type you get from a C library. I did the same thing for the SDL_Rect type when working with SDL. All I did was implement a pointer version of my "extension" functions. When I had an instance and not a pointer, I took its address when I called the function. If that's inconvenient (lots of generic code, perhaps), you can always have the pointer version forward to the ref version, but it seems to me like just having the one version is the way to go most of the time.
Dec 15 2020
parent Dave P. <dave287091 gmail.com> writes:
On Wednesday, 16 December 2020 at 03:50:07 UTC, Mike Parker wrote:
 On Sunday, 13 December 2020 at 19:02:34 UTC, Dave P. wrote:
 On Sunday, 13 December 2020 at 18:44:20 UTC, Mike Parker wrote:
 On Sunday, 13 December 2020 at 18:31:54 UTC, Dave P. wrote:
 [...]
Based on you requirement to use pointers, I assume you're doing this for a type you get from a C library. I did the same thing for the SDL_Rect type when working with SDL. All I did was implement a pointer version of my "extension" functions. When I had an instance and not a pointer, I took its address when I called the function. If that's inconvenient (lots of generic code, perhaps), you can always have the pointer version forward to the ref version, but it seems to me like just having the one version is the way to go most of the time.
Yeah, in this case the C library is the rest of my program :). I am porting an application I had written in C to D in betterC mode piece by piece and there’s a good number of functions where it’d be convenient to call them via UFCs. Thanks for answering my questions!
Dec 15 2020
prev sibling parent Marcone <marcone email.com> writes:
Two differents types; Foo type and pointer type. Need function 
overload foe each or just use ref and avoid pointer.
Dec 18 2020