A Fresh Look at Comparisons, Take 2

Janice Caron caron800 at googlemail.com
Fri Apr 18 05:03:20 PDT 2008


This is an improved proposal based on the suggestions of Steven,
Yigal, Bruce and Paul. Basically, I've ditched my original proposal
entirely, and replaced it with this one. It doesn't do exactly what
the previous proposal suggested, but nonetheless, it will still do the
"right" thing, at least in the eyes of the posters to the last thread.


RULE 1:

We have a new function paramter storage class, "explicit", which means
that only objects of the exact compile-time type may be passed in.
(Implicit casting is forbidden, apart from implicit casting to const).
So, for example:

    class A {}
    class B : A {}

    void f(explicit A a)
    {
        writefln("Got an A");
    }

    A a1 = new A;
    f(a1); /* OK */

    A a2 = new B;
    f(a2); /* OK - we only care about the compile-time type */

    B b = new B;
    f(b); /* ERROR - WILL NOT COMPILE */

However, we make an exception for implicit casts to const. Thus:

    void f(explicit const(A) a)
    {
        writefln("Got a const A");
    }

    A a1 = new A;
    f(a1); /* OK */

The new keyword "explicit" can also be applied to the "this" parameter
of member functions, as follows:

    class A
    {
        explicit void f()
        {
            writefln("Got an A");
        }
    }

    class B : A {}

    A a1 = new A;
    a1.f(); /* OK */

    A a2 = new B;
    a2.f(); /* OK - we only care about the compile-time type */

    B b = new B;
    b.f(); /* ERROR - WILL NOT COMPILE */

Observe that explicit member functions cannot be inherited, however,
the inheritance mechanism is able to "jump over" an explict function
when attempting to find a match, in order to match the next
non-explicit function. I didn't explain that very well, did I? Let me
show you what I mean by example:

    class A
    {
        int f() { writefln("In A"); }
    }

    class B : A
    {
        explicit int f() { writefln("In B"); }
    }

    class C : A
    {
    }

    A a = new A;
    B b = new B;
    C c = new C;

    a.f(); // prints "In A"
    b.f(); // prints "In B"
    c.f(); // prints "In A"

Here, the call to c.f() matches no function in C. It cannot match
B.f(), because B.f() is explicit. But it /can/ match A.f(). And so it
does. As far as objects of static type C are concerned. B's explicit
functions might as well not exist.



RULE 2:

Neither opEquals nor opCmp shall be defined in Object.


RULE 3:

When the compiler encounters an equality test, such as

    if (a == b) ...
    if (a != b) ...

and no compile-time match can be found for a.opEquals(b), then the
identity test shall be performed. That is, it shall be as if the
programmer had typed:

    if (a is b) ...
    if (a !is b) ...

Note that this rule is only applicable to classes. Structs which do
not implement opEquals shall be compared for exact bitwise equality,
as is the case currently.


RULE 4:

When the compiler encounters a comparison test other than equality, such as

    if (a < b) ...

and no compile-time match can be found for a.opCmp(b), then all such
comparisons shall be compile-time errors.

Observation: It should be possible for the programmer to test for this
at compile time, by doing, for example:

    static if (is(a < b))
    {
        ...
    }



RULE 5:

No type A may be used as an associtive array key unless a match can be
found for A.opCmp(A).

Observation: It should be possible for the programmer to test for this
at compile time, by doing, for example:

    static if (is(int[A]))
    {
        ...
    }


RULE 6:

Classes which implement opEquals and/or opCmp will be expected to
implement them using the following signatures:

    // For classes
    class C
    {
        explicit bool opEquals(explicit const(C) c) const {...}
        explicit int opCmp(explicit const(C) c) const {...}
    }

    // For structs
    struct S
    {
        explicit bool opEquals(explicit ref const(S) s) const {...}
        explicit int opCmp(explicit ref const(S) s) const {...}
    }

(However, I don't see any way of enforcing this, so this rule
represents recommended good practice).


RULE 7:

The following tokens shall be removed from the lexer, and shall no
longer have meaning in D:

    <>
    <>=
    !<>
    !<>=
    !<
    !<=
    !>
    !>=

Rationale: Every class is now either completely ordered (if opCmp is
supplied), or unordered (if not). If a class is completely ordered,
then <> is the same as !=; !> is the same as <=, and so on.
Conversely, if a class is unordered, then rule 4 makes all such
comparisons compile-time errors. Consequently, there is no longer any
need for these tests.


----

How does all that sound?



More information about the Digitalmars-d mailing list