www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Make `& Class.foo` illegal

reply Johan <j j.nl> writes:
Currently this code is not rejected by the compiler and instead 
creates non-functioning (undefined) code:
```
struct S {
     void foo() {}
}

void main() {
     auto a = &S.foo;
}
```

See https://issues.dlang.org/show_bug.cgi?id=21195 .

Considering https://www.digitalmars.com/articles/b68.html, why 
not simply reject `&S.foo` during semantic analysis?

-Johan
Aug 27 2020
next sibling parent Avrina <avrina12309412342 gmail.com> writes:
On Thursday, 27 August 2020 at 11:20:17 UTC, Johan wrote:
 Currently this code is not rejected by the compiler and instead 
 creates non-functioning (undefined) code:
 ```
 struct S {
     void foo() {}
 }

 void main() {
     auto a = &S.foo;
 }
 ```

 See https://issues.dlang.org/show_bug.cgi?id=21195 .

 Considering https://www.digitalmars.com/articles/b68.html, why 
 not simply reject `&S.foo` during semantic analysis?

 -Johan
This gets brought up often. The last issue was closed as invalid. This will likely never get fixed, the issue is that it is return a function type, when it should be a delegate type with a null "this" ptr. "Just use safe" https://issues.dlang.org/show_bug.cgi?id=3720#c16
Aug 27 2020
prev sibling next sibling parent Nathan S. <no.public.email example.com> writes:
On Thursday, 27 August 2020 at 11:20:17 UTC, Johan wrote:
 Currently this code is not rejected by the compiler and instead 
 creates non-functioning (undefined) code:
 ```
 struct S {
     void foo() {}
 }

 void main() {
     auto a = &S.foo;
 }
 ```

 See https://issues.dlang.org/show_bug.cgi?id=21195 .

 Considering https://www.digitalmars.com/articles/b68.html, why 
 not simply reject `&S.foo` during semantic analysis?

 -Johan
I sometimes compare member functions addresses to see if a class overrides a certain function. For example: --- template hasDefaultOpEquals(C) if (is(C == class)) { enum hasDefaultOpEquals = &C.opEquals is &Object.opEquals; } class C1 {} class C2 { int value; override bool opEquals(const Object rhs) const safe { if (auto o = cast(typeof(this)) rhs) return value == o.value; return false; } } static assert(hasDefaultOpEquals!C1); static assert(!hasDefaultOpEquals!C2); ---
Aug 27 2020
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Aug 27, 2020 at 11:20:17AM +0000, Johan via Digitalmars-d wrote:
 Currently this code is not rejected by the compiler and instead
 creates non-functioning (undefined) code:
 ```
 struct S {
     void foo() {}
 }
 
 void main() {
     auto a = &S.foo;
 }
 ```
 
 See https://issues.dlang.org/show_bug.cgi?id=21195 .
 
 Considering https://www.digitalmars.com/articles/b68.html, why not
 simply reject `&S.foo` during semantic analysis?
[...] In C++, there's this construct called a member function pointer, which has its own special type and requires the caller to specify an object before the function can be called. Arguably, that's what D should be implementing. If we have no intention of implementing member function pointers, then this construct should indeed be illegal, or otherwise return void* so that you cannot accidentally dereference it. I thought about making it return a function pointer with S* as the first parameter, but as Kinke pointed out in the bugnotes, the base object in a method call is treated specially and cannot be generally assumed to be the first argument to a function. So this will require a special function pointer type, or be forced to void* so that comparisons work but you can't actually call it. (Which TBH makes little sense; if we're going to allow &S.foo at all, we should do it in a thorough way and implement member function pointers properly, instead of doing a half-assed job with void*.) T -- Never step over a puddle, always step around it. Chances are that whatever made it is still dripping.
Aug 27 2020
next sibling parent Johan <j j.nl> writes:
On Thursday, 27 August 2020 at 16:30:03 UTC, H. S. Teoh wrote:
 [...]
Fully agree with what you wrote.
Aug 27 2020
prev sibling parent reply sarn <sarn theartofmachinery.com> writes:
On Thursday, 27 August 2020 at 16:30:03 UTC, H. S. Teoh wrote:
 In C++, there's this construct called a member function 
 pointer, which has its own special type and requires the caller 
 to specify an object before the function can be called.  
 Arguably, that's what D should be implementing.
I don't know if you've seen them before, but these two articles explain the reasoning behind D not having member function pointers: https://www.drdobbs.com/cpp/member-function-pointers-in-d/231600610 https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible tl;dr: C++ member function pointers are complicated when you get into the details, and the sanest implementation is with thunks that are equivalent to lambdas or delegates. Ironically, the most common usage of member function pointers turned out to be building complicated versions of lambdas and closures (at least before C++ got them built in). None of that says anything about syntax, but it's why D doesn't have member function pointers, per se, like C++ does.
Aug 27 2020
next sibling parent reply Avrina <avrina12309412342 gmail.com> writes:
On Friday, 28 August 2020 at 00:25:15 UTC, sarn wrote:
 On Thursday, 27 August 2020 at 16:30:03 UTC, H. S. Teoh wrote:
 In C++, there's this construct called a member function 
 pointer, which has its own special type and requires the 
 caller to specify an object before the function can be called.
  Arguably, that's what D should be implementing.
I don't know if you've seen them before, but these two articles explain the reasoning behind D not having member function pointers: https://www.drdobbs.com/cpp/member-function-pointers-in-d/231600610 https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible tl;dr: C++ member function pointers are complicated when you get into the details, and the sanest implementation is with thunks that are equivalent to lambdas or delegates. Ironically, the most common usage of member function pointers turned out to be building complicated versions of lambdas and closures (at least before C++ got them built in). None of that says anything about syntax, but it's why D doesn't have member function pointers, per se, like C++ does.
D does technically have them. They just aren't type safe. As other people have said, the type should be either void*, or a delegate with a null data pointer. import std.stdio; struct A { float value; void foo() { writeln(this); } } void main() { void delegate() dg; int a = 10; dg.funcptr = &A.foo; dg.ptr = &a; // no type safety, should be A* dg(); // basically equivalent to C++'s member function pointer } With the example it makes sense why &A.foo would return a pointer and not a delegate. But because there's no special type, it incorrectly is of type `void function()` when it doesn't fit that definition. Yes it doesn't technically need member function pointers, but the current implementation in D is broken (for many years) and not type safe like C++.
Aug 27 2020
parent Johan <j j.nl> writes:
On Friday, 28 August 2020 at 01:26:58 UTC, Avrina wrote:
 On Friday, 28 August 2020 at 00:25:15 UTC, sarn wrote:
 None of that says anything about syntax, but it's why D 
 doesn't have member function pointers, per se, like C++ does.
D does technically have them. They just aren't type safe. As other people have said, the type should be either void*, or a delegate with a null data pointer.
I much prefer if people refrain from making bold statements that are not facts. D does not "technically" have member function pointers. Please don't confuse what is happening in assembly for some architecture with what the language guarantees. There is no guarantee that a delegate call is the same as a method call with implicit `this` parameter. `&Struct.foo` currently has no meaning for non-static member function foo, so let's agree on fixing the compiler to error on it. If you want `&Struct.foo` to have a meaning, e.g. member function pointers, please write a DIP. (sorry for the agitated email, I'm trying to make some progress here, instead of endless debate about something off-topic) -Johan
Aug 27 2020
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Aug 28, 2020 at 12:25:15AM +0000, sarn via Digitalmars-d wrote:
[...]
 I don't know if you've seen them before, but these two articles
 explain the reasoning behind D not having member function pointers:
 https://www.drdobbs.com/cpp/member-function-pointers-in-d/231600610
 https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
[...] Thanks! I was on the fence before, but these two articles convinced me that member function pointers are not worth the trouble. Just use a delegate instead, or a lambda that receives the target object as a parameter: class C { void method1(int x); void method2(int y); } C c1 = ...; C c2 = ...; void function(C, int) mfp; mfp = (C self, int arg) { return self.method1(x); } mfp(c1, 1); // calls c1.method1(1) mfp(c2, 1); // calls c2.method1(1) mfp = (C self, int arg) { return self.method2(x); } mfp(c1, 1); // calls c1.method2(1) mfp(c2, 1); // calls c2.method2(1) This works even if the method to be bound is static. None of the specialcasing and pathological corner-cases of actual member function pointers. I say we should just forbid taking the address of non-static member functions. T -- To err is human; to forgive is not our policy. -- Samuel Adler
Aug 27 2020
prev sibling next sibling parent reply JN <666total wp.pl> writes:
On Thursday, 27 August 2020 at 11:20:17 UTC, Johan wrote:
 Currently this code is not rejected by the compiler and instead 
 creates non-functioning (undefined) code:
 ```
 struct S {
     void foo() {}
 }

 void main() {
     auto a = &S.foo;
 }
 ```

 See https://issues.dlang.org/show_bug.cgi?id=21195 .

 Considering https://www.digitalmars.com/articles/b68.html, why 
 not simply reject `&S.foo` during semantic analysis?

 -Johan
What if you want to pass a static member function to a C function as callback, wouldn't &S.foo be a valid syntax? Something like: class Mouse { extern "C" static void handleClick(int x, int y) { ... } } osAbstractionLibrary.setMouseClickHandler(&Mouse.handleClick)?
Aug 27 2020
parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 27 August 2020 at 20:48:01 UTC, JN wrote:
 What if you want to pass a static member function to a C 
 function as callback
If it is static it works fine, just non-static ones are iffy.
Aug 27 2020
prev sibling next sibling parent Boris Carvajal <boris2.9 gmail.com> writes:
On Thursday, 27 August 2020 at 11:20:17 UTC, Johan wrote:
 Currently this code is not rejected by the compiler and instead 
 creates non-functioning (undefined) code:
 struct S {
     void foo() {}
 }

 void main() {
     auto a = &S.foo;
 }
Currently, you need an instance to get a delegate, the context can be modified later to point to the desired instance: auto dg = &S.init.foo; // '&S().foo' or for classes: '&(new S()).foo' S s; dg.ptr = &s; dg();
Aug 27 2020
prev sibling parent Jacob Carlborg <doob me.com> writes:
On Thursday, 27 August 2020 at 11:20:17 UTC, Johan wrote:
 Currently this code is not rejected by the compiler and instead 
 creates non-functioning (undefined) code:
 ```
 struct S {
     void foo() {}
 }

 void main() {
     auto a = &S.foo;
 }
 ```

 See https://issues.dlang.org/show_bug.cgi?id=21195 .

 Considering https://www.digitalmars.com/articles/b68.html, why 
 not simply reject `&S.foo` during semantic analysis?

 -Johan
As others have mentioned, it's possible to manually assemble a delegate from a context pointer and a function pointer. I've used this technique in my Objective-C bridge. In that case the context pointer (or this pointer for a class) came from Objective-C and the function pointer from D. Here's a link to the code for reference [1]. [1] http://dsource.org/projects/dstep/browser/dstep/objc/bridge/Bridge.d#L377 -- /Jacob Carlborg
Aug 28 2020