Thread overview
UFCS functions with both pointers and refs
Dec 13, 2020
Dave P.
Dec 13, 2020
Mike Parker
Dec 13, 2020
Dave P.
Dec 15, 2020
Q. Schroll
Dec 15, 2020
Dave P.
Dec 17, 2020
Q. Schroll
Dec 16, 2020
Mike Parker
Dec 16, 2020
Dave P.
Dec 18, 2020
Marcone
December 13, 2020
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?


December 13, 2020
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.

December 13, 2020
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?
December 15, 2020
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.
December 15, 2020
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!
December 16, 2020
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.



December 16, 2020
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!
December 17, 2020
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.)
December 18, 2020
Two differents types; Foo type and pointer type. Need function overload foe each or just use ref and avoid pointer.