Search code examples
templatesconstraintsd

DLang - Template constraints - type must implement interface


I'm trying to convert the following C# code in to D, however I can't figure out how to make the template constraints work.

C# Implementation

public interface IComponent
{

} 

public class Container
{
    public T CreateComponent<T>() where T: IComponent, new()
    {
        // Trivial initialisation for example
        var t = new T(); 
        return t;
    }
}

D Implementaiton

public interface Component
{

}

public class Container
{
    public T createComponent(T)()
    {
        // Trivial initialisation for example
        auto t = new T();
        return t;
    }
}

How can I re-create the "where T: IComponent" constraint?

I've tried various expressions combining is, typeof etc. but can't find anything that works.


Solution

  • Well, if all you're trying to do is require that T implement the interface IComponent, then is(T : IComponent) will test that T is implicitly convertible to IComponent (which is the case when IComponent is a base class of T or an interface that it implements). So, you'd end up with something like

    public class Container
    {
        public T createComponent(T)()
            if(is(T : IComponent))
        {
            // Trivial initialisation for example
            auto t = new T();
            return t;
        }
    }
    

    Now, technically, other stuff can match if alias this is used to define an implicit conversion, which isn't likely to be common, but if you're being more paranoid about it, you could make the constraint also check that T is class - is(T == class).

    public class Container
    {
        public T createComponent(T)()
            if(is(T == class) && is(T : IComponent))
        {
            // Trivial initialisation for example
            auto t = new T();
            return t;
        }
    }
    

    Then, T must be a class, and it must implicitly convert to IComponent. However, it is still technically possible for T to be a class which doesn't implement IComponent but does define an alias this which converts to an IComponent (it's just no longer possible for T to be a struct which does that). So, it's not perfect, but I don't know of a way to ensure that an implicit conversion is done via inheritance rather than alias this. So, unfortunately, I don't know of a way to absolutely guarantee that T is a class which implements IComponent. The most that I know how to do is to ensure that it implicitly converts to an `IComponent, which almost always means that it implements it.

    However, the reality of the matter is that in the vast majority of cases, simply checking is(T : IComponent) is plenty, and depending on how your code is written, it might even work with a type which implicitly converts to IComponent but isn't actually an IComponent. So, the fact that alias this throws a spanner in the works may not actually be a problem. However, in general, alias this is a bane of generic code and why most generic code shouldn't be checking for implicit conversions. It's far too easy for a type to implicitly convert via alias this but not actually be converted in the function, in which case, it either won't compile, or it could have strange behavior if it supports the same operations that the target type does but those operations don't have the same results as they would if the original type were actually converted to the target type. So, if you actually want implicit conversions in templated code, you should force the implicit conversion by assigning the argument to the target type. But since you're looking to test for an interface rather than implicit conversions in general, what you probably want is a test which tests for that without allowing implicit conversions via alias this. Unfortunately, the only way that I know of to check at compile time whether one type derives from another or implements a specific interface is to check whether it implicitly converts to that base class or interface.

    But maybe there's some fun voodoo with traits that's actually able to do it. If there is, we should probably add something to std.traits which does that for you, and if there isn't, maybe we should find a way to add it, since alias this can definitely be annoying if you don't want it. But fortunately, it's not a problem that comes up in most code, and if you're not writing a public API, then you only have to worry about it if you're declaring types with alias this. If you're not, then it really doesn't matter.