接口和模板函数
Interface and template functions
我试图在两个不同的 类 上建立接口,其中函数的实现在子类中。它适用于常规函数,但不幸的是不适用于模板函数。
参见示例:
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());
}
(也可在线获取:https://run.dlang.io/is/Nl1edV)
结果出错:
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
我想要的可以实现吗?如果是,怎么做?
接口需要具体类型,以便编译器知道为每个 class 在虚函数 table 中保留多少槽。它还需要足够的信息来可靠地判断接口是否实际实现。
对于这样的转换案例,我只是列出需要的具体类型。 static foreach
可以提供帮助。考虑以下代码:
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());
}
经过昨晚的反复试验,我得出了另一种解决方案。它不包括 AliasSeq 并且不需要有明确的类型列表。然而,限制是你需要事先知道你所有的子类(我知道):
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());
}
我试图在两个不同的 类 上建立接口,其中函数的实现在子类中。它适用于常规函数,但不幸的是不适用于模板函数。
参见示例:
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());
}
(也可在线获取:https://run.dlang.io/is/Nl1edV)
结果出错:
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
我想要的可以实现吗?如果是,怎么做?
接口需要具体类型,以便编译器知道为每个 class 在虚函数 table 中保留多少槽。它还需要足够的信息来可靠地判断接口是否实际实现。
对于这样的转换案例,我只是列出需要的具体类型。 static foreach
可以提供帮助。考虑以下代码:
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());
}
经过昨晚的反复试验,我得出了另一种解决方案。它不包括 AliasSeq 并且不需要有明确的类型列表。然而,限制是你需要事先知道你所有的子类(我知道):
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());
}