I've made my own nullable type, and I think it works a bit more naturally than Phobos's in some cases, since it takes advantage of alias this and typeof(null) and some stuff.

(It's designed to mimic C#'s nullable types.)

Would it be a good addition to Phobos? If not, ideas on what could be improved?


//=============================

private struct NullableTag { }
template isNullable(T) { enum isNullable = __traits(compiles, T.init == null); }

template Nullable(T)
{
        static if (isNullable!(T)) { alias T Nullable; }
        else
        {
                struct Nullable
                {
                        // To tell if something is Nullable
                        private alias .NullableTag NullableTag;
                        private T _value;
                        private bool _hasValue;

                        public this(A)(auto ref A value)
                                inout /+pure @safe nothrow+/
                        {
                                this._value = value;
                                this._hasValue = true;
                        }

                        public this(A : typeof(null))(A)
                                inout /+pure @safe nothrow+/ { }

                        public @property ref const(bool) hasValue()
                                const /+pure @safe nothrow+/
                        { return this._hasValue; }

                        public @property ref inout(T) value()
                                inout /+pure @safe+/
                        { return *(this._hasValue ? &this._value : null); }

                        public int opCmp(RHS)(scope RHS rhs)
                                const /+pure @safe nothrow+/
                                if (!is(RHS.NullableTag == NullableTag))
                        {
                                int r;
                                if (this.hasValue)
                                {
                                        static if (__traits(compiles, 
this._value.opCmp(rhs)))
                                        { r = this._value.opCmp(rhs._value); }
                                        else
                                        { r = this._value < rhs ? -1 : 
(this._value > rhs ? 1 : 0); }
                                }
                                else { r = -1; }
                                return r;
                        }

                        public int opCmp(RHS)(scope RHS rhs)
                                const /+pure @safe nothrow+/
                                if (is(RHS.NullableTag == NullableTag))
                        {
                                int r;
                                if (this.hasValue && rhs.hasValue)
                                { r = 0; }
                                else if (this.hasValue && !rhs.hasValue)
                                { r = 1; }
                                else if (!this.hasValue && rhs.hasValue)
                                { r = -1; }
                                else { r = this == rhs._value; }
                                return r;
                        }

                        public int opCmp(RHS : typeof(null))(scope RHS)
                                const /+pure @safe nothrow+/
                        { return this.hasValue ? 1 : 0; }

                        public bool opEquals(RHS)(scope RHS rhs)
                                const /+pure @safe nothrow+/
                                if (!is(RHS.NullableTag == NullableTag))
                        { return this.hasValue && this._value == rhs; }

                        public bool opEquals(RHS)(scope RHS rhs)
                                const /+pure @safe nothrow+/
                                if (is(RHS.NullableTag == NullableTag))
                        {
                                return this.hasValue == rhs.hasValue &&
                                        this._value == rhs._value;
                        }

                        public bool opEquals(RHS : typeof(null))(scope RHS)
                                const /+pure @safe nothrow+/
                        { return !this.hasValue; }

                        static if (!is(T == const(T)))
                        {
                                public auto ref opAssign(RHS)(auto ref RHS rhs)
                                        /+pure @safe nothrow+/
                                {
                                        this._value = rhs;
                                        this._hasValue = true;
                                        return rhs;
                                }

                                public auto ref opAssign(RHS : 
typeof(null))(auto ref RHS rhs)
                                        /+pure @safe nothrow+/
                                {
                                        this._value = T.init;
                                        this._hasValue = false;
                                        return rhs;
                                }
                        }

                        public alias value this;
                }
        }
}

unittest
{
        Nullable!int a = null;
        Nullable!int b = 5;
        int c = 5;
        assert(a != b);
        assert(b == c);
        assert(a == null);
        assert(b != null);
        assert(b + 1 == 6);
        struct S
        {
                public bool opEquals(S) const pure @safe nothrow
                { return true; }
                public bool opEquals(int) const pure @safe nothrow
                { return true; }
        }
        Nullable!S s;
        assert(s != 0);
        assert(s.opCmp(null) == 0);
        assert(a.opCmp(null) == 0);
        assert(b.opCmp(null) > 0);
        assert(b.opCmp(6) < 0);
        assert(b.opCmp(5) == 0);
}

@property Nullable!(T) nullable(T)(auto ref T value) /+pure @safe nothrow+/
{
        Nullable!(T) result = value;
        return result;
}

Reply via email to