On Thursday, 27 September 2018 at 08:19:41 UTC, Jonathan M Davis
wrote:
On Thursday, September 27, 2018 1:41:23 AM MDT Chad Joan via
Digitalmars-d- learn wrote:
On Thursday, 27 September 2018 at 05:12:06 UTC, Jonathan M
Davis
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:
That's not really an issue with D. With classes, when you have
a == b, it
doesn't lower to a.opEquals(b). Rather, it lowers to
opEquals(a, b), and the
free function opEquals is defined as
bool opEquals(Object lhs, Object rhs)
{
// If aliased to the same object or both null => equal
if (lhs is rhs) return true;
// If either is null => non-equal
if (lhs is null || rhs is null) return false;
// If same exact type => one call to method opEquals
if (typeid(lhs) is typeid(rhs) ||
!__ctfe && typeid(lhs).opEquals(typeid(rhs)))
/* CTFE doesn't like typeid much. 'is' works, but
opEquals
doesn't
(issue 7147). But CTFE also guarantees that equal
TypeInfos are
always identical. So, no opEquals needed during
CTFE. */
{
return lhs.opEquals(rhs);
}
// General case => symmetric calls to method opEquals
return lhs.opEquals(rhs) && rhs.opEquals(lhs);
}
/************************
* Returns true if lhs and rhs are equal.
*/
bool opEquals(const Object lhs, const Object rhs)
{
// A hack for the moment.
return opEquals(cast()lhs, cast()rhs);
}
So, it already takes care of checking for null or even if the
two references point to the same object. For structs, a == b,
does lower to a.opEquals(b), but for better or worse, structs
are designed so that their init values need to be valid, or
you're going to have problems in general. Trying to work around
that is fighting a losing battle.
The spec seems to have the homogeneous cases covered: classes
with classes or structs with structs. What I'm more worried
about is stuff like when you have a class compared to a struct or
builtin type, or maybe a struct compared to a builtin type
(especially more complicated builtin types like arrays!). The
homogeneous cases are important for making a type consistent with
itself, but the other cases are important for integrating a type
with everything else in the ecosystem.
Notably, "alias this" is awesome and has more or less solved that
for me in the pedestrian cases I tend to encounter. I can write
a struct and alias this to some reference variable that will be
representative of my struct's "nullness" or other states of
existence.
But I wouldn't be surprised if there are corner-cases I haven't
encountered yet (actually I think I just remembered that this bit
me a little bit once or twice) where having a single alias-this
isn't sufficient to cover all of the possible things my
struct/class could be compared to (ex: if the type's null-state
corresponded to int.max for ints and float.nan for floats, and
you can't just use opEquals, such as when the type is a class and
could be precisely null).
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.
Maybe? It's not something that I've personally found to be
particularly
useful. Once you can templatize code, the need to have a common
base class
gets pretty hard to argue for, but I don't know that it's
non-existent.
Also, for better or worse, you can already get it with void* -
and cover
more types no less (albeit less @safely). But from what I
understand of what
Andrei is intending, ProtoObject will end up being the new root
class for
all D classos, so having ProtoObject[] would work for all
extern(D) classes.
Of course, that still potentially leaves exern(C++) classes and
interfaces
(which could be extern(C++) or COM even right now and aren't
derived from
Object). So, things are already a bit weird with classes when
you start
interacting with other languages through D. is(T : Object) and
is(T == class) do _not_ mean quite the same thing even though
you'd think
that they would. And I haven't done enough with extern(C++) or
COM in D to
claim to understand all of the subtleties. If you're not
messing with them
directly or writing generic code that's going to mess with
them, it doesn't
really matter though.
-Jonathan M Davis
Gotcha. Quite a rabbit hole :)