katafrakt
katafrakt

Reputation: 2488

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?

Upvotes: 3

Views: 99

Answers (2)

katafrakt
katafrakt

Reputation: 2488

After some trial and error last night, I came with another solution. It does not include AliasSeq and does not require to have an explicit list of types. However, the limitation is that you need to know all your subclasses beforehand (which I do):

import std.conv;
import std.stdio;

interface Num {
    // this is what's changed
    T num(T)() {
        if(cast(A) this) { return (cast(A) this).num!T(); }
        if(cast(B) this) { return (cast(B) this).num!T(); }
        throw new Exception("Unknown subclass");
    }
}

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());
}

Upvotes: 2

Adam D. Ruppe
Adam D. Ruppe

Reputation: 25595

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());
}

Upvotes: 5

Related Questions