www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Enhanced UFCS

reply xoxo <xororwr gmail.com> writes:


I’ve often found myself avoiding UFCS in D simply because I 
couldn’t use it consistently. The compiler forces me to duplicate 
functions when working with both values and pointers, which 
defeats a lot of the elegance UFCS is supposed to provide.

Here’s a minimal example:

```d
struct Vector
{
     int data;
}

void add(ref Vector self, int value)
{
     self.data += value;
}

void main()
{
     Vector v;
     Vector* ptr = &v;

     v.add(1);     // Works
     ptr.add(1);   // Error: no matching function
}
```

The ptr.add(1) line fails because UFCS doesn’t automatically 
dereference the pointer to match the ref parameter in add. To 
make it work, I’d need to either call add(*ptr, 1) or duplicate 
the function to handle Vector*. Neither option feels ideal.


Could D support automatic UFCS for pointers to structs? That is, 
allow the compiler to transform ptr.add(1) into add(*ptr, 1) if 
the function takes a ref and ptr is a pointer to the expected 
type.

This would make UFCS much more usable without changing any 
behavior for existing code. Maybe this could be enabled via auto 
ref, inout ref, or something similar if implicit dereferencing is 
too risky to do universally.


In Zig, for example, UFCS works seamlessly whether you have a 
value or a pointer — the compiler just figures it out. That kind 
of polish would be great to see in D as well.


Less boilerplate



Better parity with languages like Zig

No need to manually unwrap pointers just to get method-like syntax
May 26
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On Monday, 26 May 2025 at 07:55:20 UTC, xoxo wrote:


 I’ve often found myself avoiding UFCS in D simply because I 
 couldn’t use it consistently. The compiler forces me to 
 duplicate functions when working with both values and pointers, 
 which defeats a lot of the elegance UFCS is supposed to provide.
So the biggest caveat I see is if there are multiple options. I would say this counts as a conversion for purposes of overloading. Looks good to me though, well stated. -Steve
May 26
prev sibling next sibling parent reply Dennis <dkorpel gmail.com> writes:
I've encountered this limitation as well, and like this idea of 
more uniformity. But as Steven said, care needs to be taken with 
overloads:

```D
// foo.d
int f(S* x) => 2; // in an imported module

// bar.d
import foo;
int f(ref S x) => 1; // in this module

S* ptr;
void main()
{
     writeln(ptr.f); // 1 or 2?
}
```

On Monday, 26 May 2025 at 07:55:20 UTC, xoxo wrote:
 Here’s a minimal example:
As of 2.111, you can do `ref Vector ptr = v;` instead of using a pointer, so perhaps you can use a different motivating example.
May 26
next sibling parent Kagamin <spam here.lot> writes:
On Monday, 26 May 2025 at 14:18:36 UTC, Dennis wrote:
 As of 2.111, you can do `ref Vector ptr = v;` instead of using 
 a pointer, so perhaps you can use a different motivating 
 example.
I think, it works only for local variables. ``` char*[] args; args[0].strlen; ```
May 27
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Monday, 26 May 2025 at 14:18:36 UTC, Dennis wrote:
 I've encountered this limitation as well, and like this idea of 
 more uniformity. But as Steven said, care needs to be taken 
 with overloads:

 ```D
 // foo.d
 int f(S* x) => 2; // in an imported module

 // bar.d
 import foo;
 int f(ref S x) => 1; // in this module

 S* ptr;
 void main()
 {
     writeln(ptr.f); // 1 or 2?
 }
 ```

 On Monday, 26 May 2025 at 07:55:20 UTC, xoxo wrote:
 Here’s a minimal example:
As of 2.111, you can do `ref Vector ptr = v;` instead of using a pointer, so perhaps you can use a different motivating example.
Aren’t `foo.f` and `bar.f` in different overload sets and therefore: - Without the proposed change, `bar.f` is called since `foo.f` isn’t callable using a pointer. - With the proposed change, `foo.f` and `bar.f` are both viable in distinct overload sets, therefore it’s an ambiguity error. The proposal is a breaking change for this reason.
Jul 21
prev sibling next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 26 May 2025 at 07:55:20 UTC, xoxo wrote:

I think overload resulation is a big old mess and even small asks will break stuff unexpectedly. This could be thrown to the bottom of the list of is 6(?) rules and then only stuff using compiles check would break, but that would reduce its scaling drasticly and would still be a fight. also: ```d struct Vector { int data; } void add(T)(ref T self, int value)//<-- { self.data += value; } void main() { Vector v; Vector* ptr = &v; v.add(1); // Works ptr.add(1); // also works } ``` Id suggest trying for either: a) reusing the property keyword for ufcs functions to modify overload resulation(in my opinion the rules are exactly backwards) b) adding a type specailization that matches for T and T* c) ufcs perfers a named argument of "self"/"ufcs"/"this"
May 26
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Monday, 26 May 2025 at 07:55:20 UTC, xoxo wrote:
 Improving UFCS for Pointers to Structs
My two cents: The idea is generally good. Having to write `(*ptr).f` is annoying, considering it’s not necessary when `f` is a member of `typeof(*ptr)`, only when `f` is a free function called via UFCS. Some say it’s odd with overloading. In the imported module case, there are already overload sets in place. ```d module a; struct S { } void f(S*) { import std.stdio; writeln("a.f"); } ``` ```d module b; import a : S; void f(S) { import std.stdio; writeln("b.f"); } ``` ```d void main() { import a, b; S* p = new S; p.f(); // Current behavior: Calls a.f (*p).f(); // Current behavior: Calls b.f } ``` With your proposal, the first call becomes ambiguous. Overload resolution finds `a.f` and `b.f` and it can call both of them; because they belong to different overload sets, that’s an ambiguity error. So, one thing to note is: Your DIP can break code. It’s not overly likely, but not so unlikely that you can blatantly assume it won’t cause problems. Contrary to others, I don’t think overload resolution is a big concern though. Just define dereferencing in a UFCS call as an implicit conversion. It’s a special case, yes, but not too special.
Jun 27