Bug 2159 – Confusion between function call and C++ style function address

Status
REOPENED
Severity
enhancement
Priority
P4
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2008-06-21T15:35:56Z
Last change time
2024-12-13T17:48:31Z
Keywords
spec
Assigned to
Walter Bright
Creator
Bartosz Milewski
Moved to GitHub: dmd#17713 →

Comments

Comment #0 by bartosz — 2008-06-21T15:35:56Z
This is a very simple test case. The thread runs to completion end then throws an exception: Error: Win32 Exception import std.thread; void main() { int g; int run() { for(int i = 0; i < 1000; ++i) ++g; return 0; } auto t = new Thread(run); t.start; t.wait; }
Comment #1 by torhu — 2008-06-22T14:37:24Z
I hate to be the one to say this, but there's an important typo in your test case. try this diff: -auto t = new Thread(run); +auto t = new Thread(&run); You ended up calling the ctor that takes a size_t instead of the function pointer one.
Comment #2 by bartosz — 2008-06-22T16:20:06Z
Then this is an even more serious language-design problem. Run was called without me using the call syntax. This might be okay for no-argument methods, but it's a bad idea for non-member functions, as my example illustrates.
Comment #3 by bugzilla — 2008-06-27T17:12:17Z
This is not a language design problem, it is intended to work this way. The fact that run() worked as an argument is because run() returns an int and one of the constructors for Thread takes a size_t.
Comment #4 by bartosz — 2008-06-28T13:49:46Z
For me there's no doubt that it's a design flaw in the language and I'll try to explain my reasoning. For people coming from C/C++: A function name in C is treated as a function pointer. It's okay for D to make this syntax illegal. It's not okay to give this syntax a completely different meaning (function call), expecially if there are situations where the error is undetectable. I was lucky the the result of run() was too small for the stack size, otherwise my unit test would run without a problem. Program understanding: How does the compiler know that "run" is a call and not a variable? Because it has the symbol table. The symbol might be defined arbitrarily far from the point of use. That's fine for the compiler but not for the programmer who's reading somebody else's code. Now the reviewer must chase multiple files in order to figure out if something is a function call or a variable. You can't really say that it shouldn't matter for the reviewer whether a given statement is a function call. Notice that the analogous method-call syntax is not bad, because it has a different context. There is always an object involved. Without any knowledge of the symbol table, the programmer assumes that it's an access to a public data member. The fact that it expands to a method call is the matter of the object, whose designer decided to hide this implementation detail. This is very much in the spirit of object-oriented programming--implementation hiding. But there is a well-defined owner/encapsulator of implementation details--the object. From the abstract point of view, when you allow a symbol to transparently expand into a function call, you are making a unification. The new abstraction is a "variable or a function call". What is this abstraction? Do you have a name for it? If you don't, it's just a hack that saves two keystrokes and makes the code more obfuscated. Keystroke-saving improvements should not be a design goal in itself. Note that there is an analogous abstration for method calls. It's called a "property". When we discuss this topic in writing, you use the syntax "run()" rather than "run". That's because you want to be clear that you mean a function call. When you write a program you also communicate with other programmers. You should be as clear about the meaning as you are in our discussion. If I were reviewing somebody else's code I would demand they use the function-call syntax.
Comment #5 by davidl — 2008-06-29T22:27:10Z
yes, this is really nasty. One solution is change the interface of Thread class. But it's not a good solution. Because that relies on the higher interface design quality of lib developers(and it's not necessary in other langs). So perhaps a qualifier "property" like C# to explicit declare if this should/could be used as a property by property syntax is better. For this case, the real problem is that "run" in normal sense is not a "property", but it can be misused in property syntax.
Comment #6 by bartosz — 2008-06-30T12:08:49Z
In the meanwhile I found an example of ambiguity. Here’s some sample code from the documentation of std.algorithm: inPlace!(writeln)(arr1); // print the array Why isn't writeln executed on the spot? Are we passing a function of no arguments as an alias parameter to a template, or are we evaluating the function and passing its result to the template? Note that here additional parentheses would change the meaning of code. inPlace!(writeln())(arr1);
Comment #7 by shro8822 — 2008-06-30T12:16:57Z
switch writeln to a CTFE able function and that would be a problem. In that exact cases the alias must be used because it's the only compile-time option. And then I think it's only 2.0 that allows values as aliases.
Comment #8 by dmitry.olsh — 2018-05-15T14:32:43Z
import core.thread; void main() { int g; int run() { for(int i = 0; i < 1000; ++i) ++g; return 0; } auto t = new Thread(run); t.start; t.wait; } Now it doesn't fail and I fail to see how we fix the general since optional parens became a thing after UFCS was finally fully implemented.
Comment #9 by dfj1esp02 — 2018-05-16T12:12:16Z
> Now it doesn't fail Then it's a subtle bug that can ship to the user. > and I fail to see how we fix the general since optional > parens became a thing after UFCS was finally fully implemented. The compiler can detect overload ambiguity between constructors taking int and function pointer. > This might be okay for no-argument methods FWIW, C# takes method delegates without ampersand syntax much like C does for functions.
Comment #10 by razvan.nitu1305 — 2019-10-10T14:26:37Z
Compiling: import core.thread; void main() { int g; int run() { for(int i = 0; i < 1000; ++i) ++g; return 0; } auto t = new Thread(run); t.start; t.wait; } Yields: test.d(11): Error: class core.thread.Thread member this is not accessible test.d(13): Error: no property wait for type core.thread.Thread Is this satisfactory?
Comment #11 by dfj1esp02 — 2019-10-25T15:15:36Z
Self-contained test case: --- void f(int); void f(int function()); int g(); void h() { f(g); f(&g); } ---
Comment #12 by dfj1esp02 — 2019-10-25T15:18:14Z
If the ambiguity happened spontaneously in phobos, it's plausible it can reemerge or happen elsewhere. Though it's a language change, so it can be required to go through DIP?
Comment #13 by razvan.nitu1305 — 2019-10-25T16:19:10Z
(In reply to anonymous4 from comment #11) > Self-contained test case: > --- > void f(int); > void f(int function()); > int g(); > void h() > { > f(g); > f(&g); > } > --- I don't get it. This code compiles just fine, as it should. f(g) call s f(int) and f(&g) calls f(int function()). This seems reasonable to me.
Comment #14 by dfj1esp02 — 2019-10-28T15:16:18Z
Well, it's an enhancement request. Per the title this code is error prone and ambiguous for people with C background, although it requires specific design to trigger.
Comment #15 by josipp — 2022-04-03T19:33:36Z
(In reply to Bartosz Milewski from comment #4) > For me there's no doubt that it's a design flaw in the language and I'll try > to explain my reasoning. > > For people coming from C/C++: A function name in C is treated as a function > pointer. It's okay for D to make this syntax illegal. It's not okay to give > this syntax a completely different meaning (function call), expecially if > there are situations where the error is undetectable. I was lucky the the > result of run() was too small for the stack size, otherwise my unit test > would run without a problem. > > Program understanding: How does the compiler know that "run" is a call and > not a variable? Because it has the symbol table. The symbol might be defined > arbitrarily far from the point of use. That's fine for the compiler but not > for the programmer who's reading somebody else's code. Now the reviewer must > chase multiple files in order to figure out if something is a function call > or a variable. You can't really say that it shouldn't matter for the > reviewer whether a given statement is a function call. > > Notice that the analogous method-call syntax is not bad, because it has a > different context. There is always an object involved. Without any knowledge > of the symbol table, the programmer assumes that it's an access to a public > data member. The fact that it expands to a method call is the matter of the > object, whose designer decided to hide this implementation detail. This is > very much in the spirit of object-oriented programming--implementation > hiding. But there is a well-defined owner/encapsulator of implementation > details--the object. > > From the abstract point of view, when you allow a symbol to transparently > expand into a function call, you are making a unification. The new > abstraction is a "variable or a function call". What is this abstraction? Do > you have a name for it? If you don't, it's just a hack that saves two > keystrokes and makes the code more obfuscated. Keystroke-saving improvements > should not be a design goal in itself. > > Note that there is an analogous abstration for method calls. It's called a > "property". > > When we discuss this topic in writing, you use the syntax "run()" rather > than "run". That's because you want to be clear that you mean a function > call. When you write a program you also communicate with other programmers. > You should be as clear about the meaning as you are in our discussion. If I > were reviewing somebody else's code I would demand they use the > function-call syntax. I second this wholeheartedly; taking a page from Scala's book, they have a very similar problem on their hands, and decided to disallow no-parens invocations for anything that is not a property at some point: their reasoning was that the possibility of some call producing side effects must be clearly stated by treating the invocation as a real function call; after all, that is what the `pure` attribute is kind of trying to emulate in D, but is completely undercut by this language quirk!
Comment #16 by robert.schadek — 2024-12-13T17:48:31Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/17713 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB