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

Jonathan M Davis newsgroup.d at jmdavisprog.com
Thu Sep 27 05:12:06 UTC 2018


On Wednesday, September 26, 2018 10:20:58 PM MDT Chad Joan via Digitalmars-
d-learn wrote:
> On Wednesday, 26 September 2018 at 23:32:36 UTC, Jonathan M Davis
>
> wrote:
> > On Wednesday, September 26, 2018 3:24:07 PM MDT Adam D. Ruppe
> >
> > via Digitalmars-d-learn wrote:
> >> Object.factory kinda sux and I'd actually like to remove it
> >> (among other people). There's no plan to actually do that, but
> >> still, just on principle I want to turn people away.
> >
> > While there may not currently be plans to be remove it, as
> > there _are_ plans to add ProtoObject as the new root class
> > underneath Object, at some point here, it's likely that a large
> > percentage of classes won't have anything to do with Object, so
> > relying on Object.factory to be able to construct class Objects
> > in general isn't likely to be a viable path in the long term -
> > though presumably it would work for a code base that's written
> > specifically with it in mind.
> >
> > Personally, I'm hoping that we eventually get to the point
> > where Walter and Andrei are willing to outright deprecate
> > Object itself, but I expect that ProtoObject will have to have
> > been in use for a while before we have any chance of that
> > happening. Either way, I think that it's clear that most code
> > bases should go with a solution other than Object.factory if at
> > all reasonably possible.
> >
> > - Jonathan M Davis
>
> 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.

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.

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.

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).

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





More information about the Digitalmars-d-learn mailing list