www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Inheritance of things that are note instance methods

reply Arafel <er.krali gmail.com> writes:
Hi!

TL;DR: Inheritance seems to be a mess out of instance methods. Some 
things work, some other don't or not as expected, and apparently it 
isn't documented either [1].

Does somebody know how it's even *supposed* to work? Making it a bit 
more intuitive would also be nice :-)

Now, the long rant:

----

After a couple of threads in learn [2,3] related to how inheritance 
works in D out of the "usual" case of instance methods, I haven't been 
able to find in the spec [1] how things are supposed to be, and indeed I 
find the situation quite messy:

* Static functions are "virtual", i.e. there is a lookup of parent 
classes if it's not found. This is not documented, and I didn't even 
think it would work:

```
class A {
	static void foo() { };
}

class B : A { }

void main() {
	B.foo(); // This works, surprisingly
}
```

* However, there's no way for B.foo() to get its own class:

```
class A {
	static void foo() {
		import std.stdio;

		writeln(typeid(typeof(this)));
	}
}

class B : A { }

void main() {
	B.foo(); // This prints "A", I'd like to print "B"
}
```

This is a bit shocking, since the compiler must know for sure which 
class I'm from, and I can also make it explicit:

```
class A {
	static void foo(C)() {
		import std.stdio;

		writeln(typeid(C));
	}
}

class B : A { }

alias foo(C : A) = C.foo!C;

void main() {
	foo!B(); // This does indeed print "B"
}
```

I'd argue that since it's *already possible* (just inconvenient, 
unintuitive, and convoluted), "this" template parameters should be 
available everywhere, thus making something like this work out of the box:

```
class A {
	static void foo(this C)() {
		import std.stdio;

		writeln(typeid(typeof(this)));
		writeln(typeid(C));
	}
}

class B : A { }

void main() {
	B.foo(); // This should print "A" and then "B"
}
```

I think this is how most people would expect it to work, *given that 
B.foo() already works*. The other option would be to totally disable 
looking up static functions of parent classes.

* Finally, things get even more confusing with templates: a missing 
template will be looked up in "parent" classes, but a template that 
exists but is not instantiable won't:

```
class A {
	template foo(string s) if (s == "bar") {
		enum foo = 1;
	}
	enum bar = 1;
	
}

class B : A {
	template foo(string s) if (s == "baz") {
		enum foo = 2;
	}
	enum baz = 2;
}

void main() {
	import std.stdio;
	
	writeln(B.bar); // This works: bar is taken from A
	writeln(B.baz); // This also works, baz is directly in B
	//writeln(B.foo!"bar"); // This doesn't work: why?
	writeln(B.foo!"baz");
}
```

So all in all, I think it'd be nice to have some clear spec on what is 
supposed to work or not, and I also think that with just a couple of 
small changes things might greatly improve.

A.

[1]: https://dlang.org/spec/class.html
[2]: https://forum.dlang.org/post/olng6h$1e1s$1 digitalmars.com
[3]: https://forum.dlang.org/thread/axpqehyrvunzcjnrdccn forum.dlang.org
Aug 03
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 3 August 2017 at 12:06:02 UTC, Arafel wrote:
 Does somebody know how it's even *supposed* to work?
virtual functions can be overridden. All others work as functions across shared namespaces. Overloads across namespaces need to be explicitly added to the overload set, see: http://dlang.org/hijack.html
 * Static functions are "virtual", i.e. there is a lookup of 
 parent classes if it's not found. This is not documented, and I 
 didn't even think it would work:
It isn't virtual, more like an import. Same as member variables. idk where this is in the spec though...
 This is a bit shocking, since the compiler must know for sure 
 which class I'm from, and I can also make it explicit:
There's no place in the binary for it to pass it that information. With a non-static member, `this` is passed as a hidden function argument. With static, there explicitly are no hidden function arguments (it is compatible with outside function pointers). The template can do it since it creates several copies... and as such is NOT compatible with function pointers. The function pointer compatibility is fairly important.
 * Finally, things get even more confusing with templates: a 
 missing template will be looked up in "parent" classes, but a 
 template that exists but is not instantiable won't:
This has to do with the overload rules, see the hijack article linked above.
Aug 03
parent reply Arafel <er.krali gmail.com> writes:
On 08/03/2017 02:28 PM, Adam D. Ruppe wrote:
 
 There's no place in the binary for it to pass it that information. With 
 a non-static member, `this` is passed as a hidden function argument. 
 With static, there explicitly are no hidden function arguments (it is 
 compatible with outside function pointers). The template can do it since 
 it creates several copies... and as such is NOT compatible with function 
 pointers.
 
 The function pointer compatibility is fairly important.
 
Well, here I agree, that's why I was suggesting the "templated this" approach. I think it'd fit nicely. It would be a template and thus create as many copies as needed, meaning that if you have: ยดยดยด class A { public: static C getInstance(this C)() { // Do some stuff here return new C(); } private: this() { } } class B : A { } ``` &A.foo and &B.foo would point (as reasonable and expected, on the other hand) to different functions, and they would of course return different types. It would also follow the principle of least astonishment.
 This has to do with the overload rules, see the hijack article linked 
 above.
Well, the article only talked about functions and such, I didn't see any reference to how it would apply to templates. Still, fair enough, it looks reasonable, since one can always explicitly dispatch to the parent class if needed... a bit cumbersome, but doable. Thanks for the explanation! A.
Aug 03
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 03.08.2017 15:02, Arafel wrote:
 Well, here I agree, that's why I was suggesting the "templated this" 
 approach. I think it'd fit nicely.
https://issues.dlang.org/show_bug.cgi?id=17713
Aug 03