Bug 3345 – Static and nonstatic methods with the same name should be allowed
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P2
Component
dlang.org
Product
D
Version
D2
Platform
All
OS
All
Creation time
2009-09-26T10:37:06Z
Last change time
2021-08-04T06:18:41Z
Keywords
pull, spec
Assigned to
No Owner
Creator
Andrei Alexandrescu
Comments
Comment #0 by andrei — 2009-09-26T10:37:06Z
Consider:
class Widget {
int fun() { return 1; }
static int fun() { return 2; }
}
void main() {
writeln(Widget.fun()); // should print 2
writeln((new Widget).fun()); // should print 1
}
This should work. Otherwise there is no way to explain how .classinfo works in terms of D facilities.
Comment #1 by dsimcha — 2011-08-30T07:38:34Z
What should be the semantics if there's **only** a static method and no nonstatic method, i.e.:
class Widget {
static int fun() { return 2; }
}
void main() {
// Does this print 2 or not compile?
writeln((new Widget).fun());
}
Comment #2 by schveiguy — 2011-08-30T07:49:41Z
See proposed enhancement: issue 6579
I think David's example should not compile.
Comment #3 by dsimcha — 2011-08-30T08:27:11Z
I would argue that it should be allowed because there's no ambiguity, for two reasons:
1. That's the way it already works and changing it would break code and probably TDPL.
2. If we're going to make things more verbose, more annoying for generic code and different from what people from a C++ or Java background expect, we'd better have a compelling reason. Your example in Bug 6579 doesn't cut it. The root of the problem is that reset() is a poorly chosen name, not that allowing static methods to be called via an instance is a bad feature.
Comment #4 by schveiguy — 2011-08-30T08:47:55Z
I don't want to drag discussions for bug 6579 here, so I responded to your argument in that bug.
Comment #5 by aldacron — 2021-07-27T14:27:08Z
This recently turned up from a user in Discord, affecting opCall:
struct S {
void opCall(int x) {
}
static S opCall(int x) {
return S.init;
}
}
int main() {
auto s = S(1);
s(1);
return 0;
}
Comment #6 by sascha.orlov — 2021-07-27T15:09:30Z
struct S {
void opCall(int x) {
}
static S opCall(int x) {
return S.init;
}
}
int main() {
auto s = S(1);
s(1);
return 0;
}
yields:
source/app.d(13,15): Error: need `this` for `opCall` of type `void(int x)`
dmd failed with exit code 1.
whereas after interchanging the functions inside the struct:
struct S {
static S opCall(int x) {
return S.init;
}
void opCall(int x) {
}
}
int main() {
auto s = S(1);
s(1);
return 0;
}
yields:
source/app.d(13,15): Error: `app.S.opCall` called with argument types `(int)` matches both:
source/app.d(3,14): `app.S.opCall(int x)`
and:
source/app.d(7,10): `app.S.opCall(int x)`
dmd failed with exit code 1.
At least on an Intel Mac 10.15.7.
Comment #7 by razvan.nitu1305 — 2021-07-28T15:18:11Z
(In reply to Andrei Alexandrescu from comment #0)
> Consider:
>
> class Widget {
> int fun() { return 1; }
> static int fun() { return 2; }
> }
>
> void main() {
> writeln(Widget.fun()); // should print 2
> writeln((new Widget).fun()); // should print 1
> }
>
> This should work. Otherwise there is no way to explain how .classinfo works
> in terms of D facilities.
Currently, the staticness of a function is not considered when doing overload resolution. What the compiler does is simply check whether the function needs a this pointer or not **after** it has resolved it. This is an elegant solution and it greatly simplifies the implementation since you don't have to take staticness into account when matching the overloads. Of course, the trade off is that overloading static and non-static member functions does not work well.
I would argue that mixing static and non-static functions in the same overload set is bad practice and therefore should be discouraged. Since we can call static member functions on both instances and non-instances, why would we need something like this bug report proposes?
I suggest that we dissallow overloading static and non-static functions. It makes for an easy implementation and clear spec at the cost of having the user rename a function.
Comment #8 by b2.temp — 2021-07-28T23:01:03Z
considering that the `this` parameter is not part of the signature it's indeed reasonable not to support static and nonstatic in the same set.
```
struct S
{
void v1(){}
static void v2(){}
alias Fun = void();
static assert(is(typeof(v1) == Fun));
static assert(is(typeof(v2) == Fun));
}
```
Comment #9 by maxsamukha — 2021-07-29T11:02:29Z
(In reply to RazvanN from comment #7)
>
> I suggest that we dissallow overloading static and non-static functions. It
> makes for an easy implementation and clear spec at the cost of having the
> user rename a function.
That would be a major breaking change.
Comment #10 by maxsamukha — 2021-07-29T11:06:22Z
(In reply to Basile-z from comment #8)
> considering that the `this` parameter is not part of the signature it's
> indeed reasonable not to support static and nonstatic in the same set.
>
> ```
> struct S
> {
> void v1(){}
> static void v2(){}
>
> alias Fun = void();
>
> static assert(is(typeof(v1) == Fun));
> static assert(is(typeof(v2) == Fun));
> }
> ```
I wouldn't use the broken type system as a rationale. Static and non-static function types should have never been equal.
Comment #11 by b2.temp — 2021-07-29T11:38:20Z
(In reply to Max Samukha from comment #10)
> (In reply to Basile-z from comment #8)
> > considering that the `this` parameter is not part of the signature it's
> > indeed reasonable not to support static and nonstatic in the same set.
> >
> I wouldn't use the broken type system as a rationale. Static and non-static
> function types should have never been equal.
I'm not 100% sure of what I will say but I would say that it's not really "broken", rather that "it's pragmatically adapted to closures". You see, a kind of tradeoff.
Comment #12 by maxsamukha — 2021-07-29T14:06:25Z
(In reply to Basile-z from comment #11)
> I'm not 100% sure of what I will say but I would say that it's not really
> "broken", rather that "it's pragmatically adapted to closures". You see, a
> kind of tradeoff.
I understand it was an attempt at pragmatism, but the type system is still broken.
struct S {
void foo() {
x = 1;
}
int x;
}
void main() {
void function() f = &S.foo; // this means "broken"
f(); // bang
}
Comment #13 by alphaglosined — 2021-07-29T14:31:46Z
Add @safe to your main function.
onlineapp.d(11): Error: `this` reference necessary to take address of member `foo` in `@safe` function `main`
onlineapp.d(12): Error: `@safe` function `D main` cannot call `@system` function pointer `f`
Comment #14 by maxsamukha — 2021-07-30T09:08:23Z
(In reply to Richard Cattermole from comment #13)
> Add @safe to your main function.
>
> onlineapp.d(11): Error: `this` reference necessary to take address of member
> `foo` in `@safe` function `main`
> onlineapp.d(12): Error: `@safe` function `D main` cannot call `@system`
> function pointer `f`
Right, passing the context via the void* pointer makes a non-static function type unsafe. It is still a distinct type from the corresponding static function type because of that additional parameter.
I hope a variant of DIP1011 eventually gets through, but DIP1011 doesn't talk about fixing types of member functions and closures. It does propose to fix delegates' funcptr by introducing a properly typed alias.
Comment #15 by bugzilla — 2021-07-30T10:02:49Z
> Otherwise there is no way to explain how .classinfo works in terms of D facilities.
It is explained by if the member function is static, the 'this' reference is used for its type.
writeln(Widget.fun()); // Widget is a type
writeln((new Widget).fun()); // (new Widget) gives the type
This is so that member functions that don't need the `this` reference can still work fine if the `this` reference is provided.
This explains the overloading behavior. The compiler and language is working as designed.
For code like this:
int fun() { return 1; }
static int fun() { return 2; }
I cannot understand what purpose there is for writing such code.
Comment #16 by razvan.nitu1305 — 2021-07-30T10:12:52Z
(In reply to Walter Bright from comment #15)
> > Otherwise there is no way to explain how .classinfo works in terms of D facilities.
>
> It is explained by if the member function is static, the 'this' reference is
> used for its type.
>
> writeln(Widget.fun()); // Widget is a type
> writeln((new Widget).fun()); // (new Widget) gives the type
>
> This is so that member functions that don't need the `this` reference can
> still work fine if the `this` reference is provided.
>
> This explains the overloading behavior. The compiler and language is working
> as designed.
>
> For code like this:
>
> int fun() { return 1; }
> static int fun() { return 2; }
>
> I cannot understand what purpose there is for writing such code.
But having different error messages depending on the order of declarations is definitely wrong.
Comment #17 by bugzilla — 2021-07-30T10:23:00Z
> But having different error messages depending on the order of declarations is definitely wrong.
Probably, but it's a minor issue, as both messages are correct.
Comment #18 by razvan.nitu1305 — 2021-07-30T10:24:17Z
@WalterBright created dlang/dlang.org pull request #3080 "fix Issue 3345: Static and nonstatic methods with the same name shoul…" fixing this issue:
- fix Issue 3345: Static and nonstatic methods with teh same name should be allowed
https://github.com/dlang/dlang.org/pull/3080
Comment #21 by dlang-bot — 2021-08-02T08:04:40Z
dlang/dlang.org pull request #3080 "fix Issue 3345: Static and nonstatic methods with the same name shoul…" was merged into master:
- 4bb31c3d5533cf048856bd1ea92f664b22bf9d25 by Walter Bright:
fix Issue 3345: Static and nonstatic methods with teh same name should be allowed
https://github.com/dlang/dlang.org/pull/3080
Comment #22 by maxsamukha — 2021-08-04T05:59:42Z
(In reply to Walter Bright from comment #15)
> > Otherwise there is no way to explain how .classinfo works in terms of D facilities.
> For code like this:
>
> int fun() { return 1; }
> static int fun() { return 2; }
>
> I cannot understand what purpose there is for writing such code.
It is a standard OOP practice. For example, 'Equals' in .NET's root class https://docs.microsoft.com/en-us/dotnet/api/system.object?view=net-5.0#methods.
Comment #23 by maxsamukha — 2021-08-04T06:18:41Z
(In reply to Walter Bright from comment #17)
> > But having different error messages depending on the order of declarations is definitely wrong.
>
> Probably, but it's a minor issue, as both messages are correct.
If there is a matching static overload, "need 'this'" without "matches both" is misleading. I've filed https://issues.dlang.org/show_bug.cgi?id=22157 for that.