Thread overview
UDA inheritance
Sep 10, 2020
drug
Sep 10, 2020
Adam D. Ruppe
September 10, 2020
Hello folks,

Is there any way to define UDAs such that they automatically inherit other UDA definitions?

For example, suppose I define:

    enum BaseUDA { A, B }

Is there a way to define `AnotherUDA` such that if `hasUDA!(T, AnotherUDA)` then it is a given that `hasUDA!(T, BaseUDA)` will also be true?  (And similarly for the `A`, `B` specializations?)

The use-case here is to create a UDA that defines some general distinction of code properties, and to allow downstream code to define its own more specialized cases of that distinction.

Thanks in advance for any thoughts or advice!

Thanks and best wishes,

      -- Joe
September 10, 2020
On 9/10/20 4:06 PM, Joseph Rushton Wakeling wrote:
> Hello folks,
> 
> Is there any way to define UDAs such that they automatically inherit other UDA definitions?
> 
> For example, suppose I define:
> 
>      enum BaseUDA { A, B }
> 
> Is there a way to define `AnotherUDA` such that if `hasUDA!(T, AnotherUDA)` then it is a given that `hasUDA!(T, BaseUDA)` will also be true?  (And similarly for the `A`, `B` specializations?)
> 
> The use-case here is to create a UDA that defines some general distinction of code properties, and to allow downstream code to define its own more specialized cases of that distinction.
> 
> Thanks in advance for any thoughts or advice!
> 
> Thanks and best wishes,
> 
>        -- Joe

Just a thought - couldn't you use classes for this? Get an UDA and check if it is a descendant of the specific class.
September 10, 2020
On Thursday, 10 September 2020 at 13:14:47 UTC, drug wrote:
> Just a thought - couldn't you use classes for this? Get an UDA and check if it is a descendant of the specific class.

Yes, I did wonder about that, but it doesn't allow all the inference that I'm looking for.  For example:

    class First
    {
        enum A;
        enum B;
    }

    class Second : First
    {
    }


    @(Second.A)
    struct MySecond
    {
    }


    import std.traits : hasUDA;

    static assert(hasUDA!(MySecond, Second.A));  // OK!
    static assert(hasUDA!(MySecond, First.A));   // OK!
    static assert(hasUDA!(MySecond, Second));    // fails
    static assert(hasUDA!(MySecond, First));     // fails

It's obvious _why_, of course, given how I set the above up.  And this contrasts with what one can do with enums:

    enum Something { A, B }

    @(Something.A)
    struct MySomething { }

    import std.traits : hasUDA;

    static assert(hasUDA!(MySomething, Something.A));  // OK!
    static assert(hasUDA!(MySomething, Something));    // OK!

... where I can check for the enum specialization _or_ the general enum type and have things just work.

I'm looking, ideally, to be able to do both.  I did try something like:

    enum First { A, B }

    enum Second : First { A = First.A, B = First.B }

... but that doesn't work.  Hence why I thought it might be worth making a forum post.
September 10, 2020
On Thursday, 10 September 2020 at 13:06:41 UTC, Joseph Rushton Wakeling wrote:
> `hasUDA!(T, AnotherUDA)`

...do you have to use hasUDA?

The language works with class UDAs, but hasUDA doesn't support it.

If you write your own test function though you can:

``import std.traits;

class BaseUDA {
        static BaseUDA opCall(string a) {
                return new BaseUDA(a);
        }

        string a_;
        string a() { return a_; }

        this(string a) {
                this.a_ = a;
        }
}


class DerivedUDA : BaseUDA {
        static DerivedUDA opCall(string a) { return new DerivedUDA(a); }
        this(string a) { super(a); }
}


@DerivedUDA("test") struct Test {}

BaseUDA hasOurUda(T)() {
        BaseUDA found = null;
        foreach(uda; __traits(getAttributes, T)) {
                static if(is(typeof(uda) : BaseUDA))
                        found = uda;
        }
        return found;
}

pragma(msg, hasOurUda!Test);

```

Or, of course, if you are making your own function, you can still do a plain type == a || b, but the class is fairly natural for more user extension.

the opCall there is not necessary, it just saves the `new`. This is equally valid:

@(new DerivedUDA("test")) struct Test {}


You could also use static immutable class instances kinda like enum instances:


---

import std.traits;

class BaseUDA {
        string a_;
        string a() const { return a_; }

        this(string a) immutable {
                this.a_ = a;
        }


        static immutable A = new immutable BaseUDA("A");
        static immutable B = new immutable BaseUDA("B");
}


class DerivedUDA : BaseUDA {
        this(string a) immutable { super(a); }

        static immutable C = new immutable DerivedUDA("C");
}

// or use B or C or wahtever
@(DerivedUDA.A) struct Test {}

pragma(msg, __traits(getAttributes, Test));

immutable(BaseUDA) hasOurUda(T)() {
       // awkward to work around unreachable code warning
        BaseUDA found = null;
        foreach(uda; __traits(getAttributes, T)) {
                static if(is(typeof(uda) : const BaseUDA))
                        found = cast() uda;
        }
        return cast(immutable) found;
}

pragma(msg, hasOurUda!Test);

---



so if you are willing to write your own test function there's a lot of options to consider.

But if you're stuck with Phobos... well, back to the drawing bard.