Is there a way to use Object.factory with templated classes? Or some way to construct templated classes given RTTI of an instance?

Chad Joan chadjoan at gmail.com
Thu Sep 27 07:41:23 UTC 2018


On Thursday, 27 September 2018 at 05:12:06 UTC, Jonathan M Davis 
wrote:
> On Wednesday, September 26, 2018 10:20:58 PM MDT Chad Joan via 
> Digitalmars- d-learn wrote:
>> ...
>>
>> That's interesting!  Thanks for mentioning.
>>
>> If you don't mind, what are the complaints regarding Object?  
>> Or can you link me to discussions/issues/documents that point 
>> out the shortcomings/pitfalls?
>>
>> I've probably run into a bunch of them, but I realize D has 
>> come a long way since that original design and I wouldn't be 
>> surprised if there's a lot more for me to learn here.
>
> I can point you to the related DIP, though it's a WIP in 
> progress
>
> https://github.com/andralex/DIPs/blob/ProtoObject/DIPs/DIPxxxx.md
>
> There are also these enhancement requests for removing the 
> various member functions from Object (though they're likely to 
> be superceded by the DIP):
>
> https://issues.dlang.org/show_bug.cgi?id=9769 
> https://issues.dlang.org/show_bug.cgi?id=9770 
> https://issues.dlang.org/show_bug.cgi?id=9771 
> https://issues.dlang.org/show_bug.cgi?id=9772
>
> Basically, the problems tend to come in two areas:
>
> 1. Because of how inheritance works, once you have a function 
> on a class, you're forcing a certain set of attributes on that 
> function - be it type qualifiers like const or shared or scope 
> classes like pure or @safe. In some cases, derived classes can 
> be more restricted when they override the function (e.g. an 
> overide can be @safe when the original is @system), but that 
> only goes so far, and when you use the base class API, you're 
> stuck with whatever attributes it has. Regardless, derived 
> classes can't be _less_ restrictive. In fact, the only reason 
> that it's currently possible to use == with const class 
> references in D right now is because of a hack. The free 
> function opEquals that gets called when you use == on two class 
> references actually casts away const so that it can then call 
> the member function opEquals (which doesn't work with const). 
> So, if the member function opEquals mutates the object, you 
> actuall get undefined behavior. And because Object.opEquals 
> defines both the parameter and invisible this parameter as 
> mutable, derived classes have to do the same when they override 
> it; otherwise, they'd be overloading it rather than overriding 
> it.
>

You're right, I wouldn't be caught dead wearing that.

:)

But yeah, thanks for pointing that out.  Now I know not to mutate 
things in an opEquals, even if it makes sense from the class's 
point of view, just in case.  At least until this all gets sorted 
out and code gets updated to not inherit from Object.

> Object and its member functions really come from D1 and predate 
> all of the various attributes in D2 - including const. But even 
> if we could just add all of the attributes that we thought 
> should be there without worrying about breaking existing code, 
> there would be no right answer. For instance, while in the vast 
> majority of cases, opEquals really should be const, having it 
> be const does not work with types that lazily initialize some 
> members (since unlike in C++, D does not have backdoors for 
> const - when something is const, it really means const, and 
> it's undefined behavior to cast away const and mutate the 
> object). So, having Object.opEquals be const might work in 99% 
> of cases, but it wouldn't work in all. The same could be said 
> for other attributes such as pure or nothrow. Forcing a 
> particular set of attributes on these functions on everyone is 
> detrimental. And honestly, it really isn't necessary.
>
> Having them on Object comes from a Java-esque design where you 
> don't have templates. With proper templates like D2 has, there 
> normally isn't a reason to operate on an Object. You templatize 
> the code rather than relying on a common base class. So, 
> there's no need to have Object.toString in order have toString 
> for all classes or Object.opEquals to have opEquals for all 
> classes. Each class can define it however it sees fit. Now, 
> once a particular class in a hierarchy has defined a function 
> like opEquals or toString, that affects any classes derived 
> from it, but then only the classes derived from it are 
> restricted by those choices, not every single class in the 
> entire language as has been the case with Object.
>

That makes sense.  Also, compile-time inheritance/duck-typing 
FTW, again.

This is also reminding me of how it's always bugged me that there 
isn't a way to operator overload opEquals with a static method 
(or even a free function?), given that it would allow the 
class/struct implementer to guard against (or even interact 
intelligently with) null values:

import std.stdio;

class A
{
	int payload;

	bool opEquals(int rhs)
	{
		if ( rhs == int.max )
			return false;
		else
			return this.payload == rhs;
	}
}

class B
{
	int payload;

	static bool opEquals(B lhs, int rhs)
	{
		if ( lhs is null && rhs == int.max )
			return true;
		else
		{
			if ( rhs == int.max )
				return false;
			else
				return lhs.payload == rhs;
		}
	}
}

void main()
{
	A a1 = new A();
	assert(a1 != int.max);

	/+A a2 = null;
	if ( a2 == int.max )
		writeln("Even though it'd be nice to compare these things, "~
			"we should crash before this writeln.");
	+/

	B b2 = null;
	//if ( b2 == int.max )
	if ( B.opEquals(b2, int.max) )
		writeln("Correct!");
	else
		assert(0);
}


> 2. The other big issue has been that built-in monitor. It 
> allows us to have synchronized classes, but in most cases, it's 
> unnecessary overhead. _Most_ classes don't do anything with 
> synchronized, so why have the monitor? It really should just be 
> in those classes that need it. With Object as the base class 
> for all D class, every class gets it whether it needs it or 
> not. With the ProtoObject DIP, only those classes which 
> specifically ask for it (or which don't bother to specify a 
> base class and thus continue to use Object as their base class) 
> will continue to have a monitor object.
>

Makes sense (to fix).

> A related issue that Andrei likes to bring up occasionally 
> (though I don't think that much of anyone else has complained 
> about) is that synchronized is one of those things that the 
> language can do that we can't duplicate without the languages 
> help. With synchronized, you can have a const or immutable 
> object with a mutex inside it which works perfectly fine, but 
> without synchronized, that's not possible because of the 
> transitivity of const and immutable. synchronized and the 
> monitor object give us a backdoor that we can't emulate, and 
> Andrei doesn't like language features where the language has a 
> superpower that you can't emulate (another, unrelated example 
> that he likes to bring up sometimes would be how when you pass 
> a dynamic array to a templated function, it's instantiated with 
> the tail-const version of the type, which doesn't work with 
> user-defined types and actually would pose some interesting 
> problems to implement for user-defined types).
>

I can sympathize.  I really get this sour feeling every time I 
want to write a really smooth type that behaves like it came with 
the language and integrates with everything really well, only to 
realize that there are various unsolvable corner-cases that poke 
holes in it.  D is still much better at this operator overloading 
thing than any of the other languages I've used, but it'd be so 
much better if it were just completely *perfect* at it ;)

I'm uh, all too used to languages just lopping off all of my arms 
and legs (just don't implement operator overloading, because that 
solves the problem!) and then saying that everyone is equal 
(because everyone is a basket case now).  Merely a flesh wound 
etc etc...

> So, in any case, because of D's powerful template system, 
> there's no need to have any member functions on Object. There 
> arguably isn't even any need to have any root class type. But 
> having a root class type with member functions has proven to be 
> a _big_ problem when attributes come into play and a minor one 
> with regards to unnecessary overhead because of synchronized 
> classes.
>
> - Jonathan M Davis

Wouldn't it be helpful to have a root class type just to have a 
"Top" type at runtime, even if it had no members?  Ex: so you 
could do things like make an array ProtoObject[] foo; that can 
contain any runtime polymorphic variables.


Thank you for the enlightening post and thorough explanation.

It's been a while since I've been able to do any D programming, 
so it's nice to come back and see all of the thoughtful 
considerations behind it and the thoughtfulness generally found 
in this community.  I appreciate it.



More information about the Digitalmars-d-learn mailing list