Interface and template functions

流过昼夜 提交于 2019-12-10 20:40:33

问题


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?


回答1:


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



回答2:


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


来源:https://stackoverflow.com/questions/57599073/interface-and-template-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!