Search code examples
dfunction-templates

Interface and template functions


I'm trying to have interface over two different classes where implementation of a function is in the subclass. It works for regular functions, but unfortunately not for template functions.

See example:

import std.conv;
import std.stdio;

interface Num {
    T num(T)();
}

class A : Num {
    T num(T)() {
        return 5.to!T;
    }
}

class B : Num {
    T num(T)() {
        return 2.to!T;
    }
}

void main() {
    auto a = new A();
    auto b = new B();
    Num somea = a;
    Num someb = b;
    writeln(a.num!int());
    writeln(somea.num!int());
    writeln(someb.num!int());
    writeln(somea.num!string());
    writeln(someb.num!string());
}

(also available online: https://run.dlang.io/is/Nl1edV)

The result is an error:

onlineapp.d:26: error: undefined reference to '_D9onlineapp3Num__T3numTiZQhMFZi'
onlineapp.d:27: error: undefined reference to '_D9onlineapp3Num__T3numTiZQhMFZi'
onlineapp.d:28: error: undefined reference to '_D9onlineapp3Num__T3numTAyaZQjMFZQj'
onlineapp.d:29: error: undefined reference to '_D9onlineapp3Num__T3numTAyaZQjMFZQj'
collect2: error: ld returned 1 exit status
Error: linker exited with status 1

Is what I want possible to achieve? If so, how?


Solution

  • Interfaces need concrete types so the compiler knows how many slots to reserve in the virtual function table for each class. It also needs enough information to reliably tell if the interface is actually implemented.

    For a case of conversion like this, I'd just list out the specific types needed. static foreach can help. Consider the following code:

    import std.conv;
    import std.stdio;
    import std.meta : AliasSeq; // used for the static foreach list
    
    interface Num {
        // this is the templated interface. You are allowed to have final
        // template members in an interface, with a body included.
        public final T num(T)() {
           T tmp;
           numImpl(tmp); // this forwards to the virtual function...
           return tmp;
        }
    
        // Here is the explicit list of types we want supported in the interface
        // it must be listed so the compiler knows how many vtable slots to assign
        protected alias NumImplTypes = AliasSeq!(int, string);
        // and now it declares the functions. To follow D overload rules, the
        // arguments for each must be different; we can't just rely on return
        // types. That's why I did it as a ref thing.
        static foreach(T; NumImplTypes)
                protected void numImpl(ref T t);
    }
    
    class A : Num {
        // and now, in each child class, we just do the foreach implementation, 
        // looking very similar to the template. But it isn't a template anymore
        // which allows it to be virtual.
        static foreach(T; NumImplTypes)
        protected void numImpl(ref T t) {
            t = 5.to!T;
        }
    }
    
    class B : Num {
        // ditto
        static foreach(T; NumImplTypes)
        protected void numImpl(ref T t) {
            t = 2.to!T;
        }
    }
    
    // this is the same as in your example
    void main() {
        auto a = new A();
        auto b = new B();
        Num somea = a;
        Num someb = b;
        writeln(a.num!int());
        writeln(somea.num!int());
        writeln(someb.num!int());
        writeln(somea.num!string());
        writeln(someb.num!string());
    }