Search code examples
templatesdtemplate-specialization

How to get the template argument of an alias-this-struct via is-expression in D?


I was playing around with alias this which can simulate inheritance for structs in D. For a library that I recently started I also would like to provide trait templates which detect the proper type via is-expression.

Here is my type hierarchy.

struct R(X) {
    S!X s;
    alias s this;
    
    this(X i) { s = i; }
}

struct S(X) {
    T a;
    alias T = X;
    alias a this;
    
    this(T t) { a = t; }
}

import std.stdio : writeln;
void main()
{
    R!int r = 3;
    S!int s = r;
    int i = r;
    writeln(r, " ", s, " ", i );
    
    static assert( is(r.s.T) && is(typeof(r) : S!(r.s.T)) );
    
    static assert( is(typeof(r) : R!X, X) );
    
    static assert( is(typeof(r) : int) && is(typeof(r) : S!int) );
    
    alias T = int;
    static assert( is(typeof(r) : S!T) );
    
    static assert(
        is(typeof(r) : S!Q, int Q) ||
        is(typeof(r) : S!Q, Q) ||
        is(typeof(r) : S!Q, r.s.T Q) ||
        is(typeof(r) : R!Q, int Q) ||
        is(typeof(r) : R!Q, r.s.T Q)
    );
}

I would expect all the static asserts to be true, so it is able to match a generic template argument via template specialization. But in reality, the last static assert is false! This is not just rdmd or dmd but also ldc2. I suppose, I made something wrong here.

My question is, how to extract the generic template argument X from type S!X properly without using an alias r.s.T from my example? I'd like to do that via is-expression.

This question is specifically about struct. For performance and proramming experience, I will not use class inheritance instead of alias this. My struct will only be a small wrapper around a member field whose main purpose is overloading of some operators.

Thank you.


Solution

  • You need to use is INSIDE static if to do the extraction.

        static if(is(typeof(r) == R!Q, Q))
            pragma(msg, Q); // will print that int, you can just use Q as a type inside this branch
        else
            // not an instance of R
    

    is(typeof(r) : R!Q, r.s.T Q) means the type of r implicitly casts to R!Q where Q is a value of type int. So that would be like R!4 or something. Same with the previous two and the first lines, they are looking for Q to be a value, not a type. Only the second item there is in the correct form to match this at all.... and I would guess it doesn't just because the compiler isn't smart enough to look that deep into it. Like a lot of is exprs realistically work best with one level of ask - implicit convert OR extract args. Here it asks to do both and tbh prolly a compiler bug / lazy impl.