Too many attributes?

Janice Caron caron800 at googlemail.com
Fri Apr 25 09:49:12 PDT 2008


On 25/04/2008, Yigal Chripun <yigal100 at gmail.com> wrote:
>  D2 will allow the following:
>  pure invariant invariant(int) func(invariant(int)) nothrow;
>  Am I the only one that thinks the above is too much?

You're not the only one.

Just for the record though, invariant(int) will implicitly cast to and
from int, so the above could be simiplified to

    pure invariant int func(int) nothrow;

However, the following cannot be simplified

    pure invariant invariant(C) func(invariant(C) c) nothrow;

where C is a class. And it gets worse. Consider the following code:

    pure int func(invariant(C) a, invariant(C) b)
    {
        if (c == b) ...

Whoops! That won't compile, because - guess what!? - opEquals isn't pure!!!!

And it doesn't stop there - opEquals cannot be made pure just by
changing its declaration, because a pure function must have invariant
arguments, and opEquals has to work with or without invariant data.
The upshot is that, eventually, Object.opEquals may have to be
implemented twice:

    class Object
    {
        // the normal version
        const bool opEquals(const Object o) {...}

        // the pure version
        pure invariant bool opEquals(invariant Object o) {...}
    }

and, therefore, so will anything that overloads it.

And then there are delegates. I have mentioned in a previous thread
(called something like "big hole in const system") that big problems
exist because attributes can be applied to functions /but not to
delegates/. That is, we cannot declare

        pure invariant invariant(C) delegate(invariant(C) c) nothrow dg;

If we take the address of func, where func is declared as

    pure invariant invariant(C) func(invariant(C) c) nothrow;

then &func will have type

    invariant(C) delegate(invariant(C))

That is, all the attributes will have been lost. That makes it
possible to break any contract implied by the attributes without the
compiler noticing, /just by taking the address of a function/.

And then we come to templates. Oh my! These new attributes are going
to cause a big problem for templates. Consider:

    ReturnType!(f) foo(alias f)(ParameterTypeTuple!(f) n)
    {
        return f(n);
    }

or should that be...

    ReturnType!(f) foo(alias f)(ParameterTypeTuple!(f) n) nothrow
    {
        return f(n);
    }

? Hmm. Looks like the real answer is

    template foo(alias f)
    {
        if(is(f == nothrow))
        {
            ReturnType!(f) foo(ParameterTypeTuple!(f) n) nothrow
            {
                return f(n);
            }
        }
        else
        {
            ReturnType!(f) foo(ParameterTypeTuple!(f) n)
            {
                 return f(n);
            }
        }
    }

(and even that's assuming that we will be able to test for "if(is(f ==
nothrow))", which also is not certain). In fact, really, the template
should also test for "pure" as well, so it can attach the "pure"
attribute if it can.

And just in case anyone thinks that was a contrived example, real
world uses would include root-solving by Newton's method, root-solving
by the secant method, numerical integration, etc. - all of which will
be pure iff f is pure, nothrow iff f is nothrow, etc.

While I understand the reasons for "pure", etc. (- and don't
misunderstand me, they are sound reasons!), there are implications for
syntax which will come back and bite us if we're not careful. At the
very least, I would suggest

(1) attributes for delegates
(2) attribute tuples for templates

(e.g.

    AttributeTuple!(f) ReturnType!(f) foo(alias f)(ParameterTypeTuple!(f) n)
    {
        return f(n);
    }

)



More information about the Digitalmars-d mailing list