Tam*_*mas 4 templates struct d
我可以编写一个包含AB结构的所有成员的结构,A并且B:
template AFields() {int a;}
struct A { mixin AFields; }
template BFields() {int b;}
struct B { mixin BFields; }
struct AB { mixin AFields; mixin BFields; }
A a; a.a = 1;
B b; b.b = 2;
AB ab; ab.a = 3; ab.b = 4;
Run Code Online (Sandbox Code Playgroud)
但我怎么能建设AB,如果我没有在控制A和B我没有AFields和BFields?即如何编写CatStruct模板,以便下面的代码编译?
struct A { int a; }
struct B { int b; }
mixin CatStruct!("AB", A, B);
AB ab;
ab.a = 1; ab.b = 2;
Run Code Online (Sandbox Code Playgroud)
标准库有几个隐藏的珠宝,我实际上甚至不知道自己,直到我偷看源来回答这个问题:
http://dlang.org/phobos/std_traits.html#Fields
以及它下面的那些.有了这些,我们可以CatStruct相当简洁.看吧:
mixin template CatStruct(string name, T...) {
static import std.traits, std.conv;
private string _code_generator() {
string code = "struct " ~ name ~ " {";
foreach(oidx, t; T) {
foreach(idx, field; std.traits.FieldTypeTuple!t)
// this line is a monster, see the end of this answer
code ~= "std.traits.FieldTypeTuple!(T["~std.conv.to!string(oidx)~"])["~std.conv.to!string(idx)~"] "~ std.traits.FieldNameTuple!t[idx] ~ ";";
}
code ~= "}";
return code;
}
mixin(_code_generator());
}
Run Code Online (Sandbox Code Playgroud)
这使用了一个字符串mixin虽然...虽然字符串mixins基本上可以做任何事情,但它们也基本上很糟糕.这可能很脆弱但我认为它基本上可以工作,而基本上是吸吮.
它也不会做结构方法,但我认为对于任何这些神奇的东西来说实在太难了,除了或许opDispatch,正如在另一个答案中看到的那样(这是非常好的顺便说一下,不要把我的答案视为否定那个,只是另一个想法).
如果两个结构之间存在冲突的名称,它们将破坏这一点,并且您将从编译器中获得一个可怕的丑陋错误消息.使用真正的模板mixin,可以轻松解决这个问题 - 命名模板mixin,它允许您消除歧义.但这里没有这样的事情.如果你需要它,我想你可以破解一个.
但无论如何,可能有一种方法可以使用这些FieldTypeTuple并FieldNameTuple从stdlib中做到这一点甚至更好,但我认为它或多或少是你现在要求的.
顺便说一下,如果你可以的话,我会说只做普通的作文,它总体上会发挥最好的作用.(不要忘记alias this也可以自动转发成员变量.)
如果你还没有做很多mixins,你可能想问我为什么在code ~=部分使用那个疯狂的字符串而不是更简单.code ~= field.stringof ~ " "~ FieldNameTuple!t[idx] ~ ";";
tl; dr:请相信我,总是使用可用于mixin()您生成的代码中运行自身的范围的本地名称.很长的解释如下/
它与名称冲突和符号查找有关.我在混合代码中使用了静态导入和完全限定名称 - 包括使用FieldTypeTuple的本地符号而不是field.stringof - 以使其尽可能保持名称空间整齐.
考虑结构A在内部导入其他模块并使用它定义字段的情况.
// using my color.d just cuz I have it easily available
// but it could be anything, so don't worry about downloading it
struct A { import arsd.color; Color a; }
AB ab;
import arsd.color;
ab.a = Color.white; ab.b = 2; // we expect this work, should be the same type
Run Code Online (Sandbox Code Playgroud)
由于这是结构A中的本地导入,因此该名称在mixin处无意义.
继续调整mixin,使用简单的线条进行编译
// comment fancy line
// code ~= "std.traits.FieldTypeTuple!(T["~std.conv.to!string(oidx)~"])["~std.conv.to!string(idx)~"] "~ std.traits.FieldNameTuple!t[idx] ~ ";";
// paste in simple line
code ~= field.stringof ~ " "~ std.traits.FieldNameTuple!t[idx] ~ ";";
Run Code Online (Sandbox Code Playgroud)
并编译:
$ dmd f.d ~/arsd/color.d
f.d-mixin-31(31): Error: undefined identifier 'Color'
f.d(4): Error: mixin f.CatStruct!("AB", A, B) error instantiating
Run Code Online (Sandbox Code Playgroud)
Zoinks!它不知道字符串"Color"应该引用什么.如果我们在本地模块中导入了一些其他类型的struct Color,它将编译....但是它会引用不同的类型:
struct A { import arsd.color; Color a; }
struct B { int b; }
struct Color { static Color white() { return Color.init; } }
mixin CatStruct!("AB", A, B);
AB ab;
import arsd.color;
ab.a = Color.white; ab.b = 2;
Run Code Online (Sandbox Code Playgroud)
编译它并看到一个愚蠢的声音错误:
$ dmd f.d ~/arsd/color.d
f.d(12): Error: cannot implicitly convert expression (white()) of type Color to Color
Run Code Online (Sandbox Code Playgroud)
BTW:记住这个,如果你在野外看到它 - 编译器错误消息听起来很荒谬,"不能隐式地将Color转换为Color",但它实际上确实具有逻辑意义:在不同的模块中只有两个不同的类型具有相同的名称.
无论如何,这听起来很愚蠢,但有意义,因为两个范围导入了不同的结构.
FieldTypeTuple使用与本地静态导入一起使用的long-form ,它总是引用传入的实际类型.间接,确定,但也毫不含糊.
我向那些已经知道字符串mixins的陷阱的读者道歉,但是在搜索中发现这一点的人可能不知道为什么我使用那些错综复杂的代码.我发誓,由于实际问题的真实世界经验很复杂!:)第一次做正确比试图调试奇怪的废话更容易,它可以带来另一种方式.
这里有很多理由(成员,功能,模板等).但是,这是一个让你入门的想法:
import std.typecons;
struct A { int a; }
struct B { int b; }
struct AB
{
mixin MultiProxy!(A, B);
}
mixin template MultiProxy(A, B) {
private A _a;
private B _b;
mixin Proxy!_a aProxy;
mixin Proxy!_b bProxy;
template opDispatch(string op) {
static if (is(typeof(aProxy.opDispatch!op))) {
alias opDispatch = aProxy.opDispatch!op;
}
else {
alias opDispatch = bProxy.opDispatch!op;
}
}
}
unittest
{
AB ab;
ab.a = 4;
ab.b = 5;
assert(ab.a == 4);
assert(ab.b == 5);
}
Run Code Online (Sandbox Code Playgroud)
我没有时间对此进行全面测试,所以如果有很多方面可以解决这个问题,我不会感到惊讶(只需看看实施情况Proxy,看看它必须考虑的所有事情).
但是,一般的想法是创建两个代理,每个代理明确命名(aProxy,bProxy),因此我们可以显式调用其中opDispatch任何一个,具体取决于哪些代理将被编译.
为了完整性,这里有一个使用命名元组的解决方案:
import std.meta, std.traits, std.typecons;
// helper template to interleave 2 alias lists
template Interleave(A...)
{
static if(A.length == 0)
alias A Interleave;
else
alias AliasSeq!(A[0], A[A.length/2],
Interleave!(A[1..A.length/2], A[A.length/2+1..$])) Interleave;
}
// helper template to produce tuple template parameters
template FieldTypeNameTuple(A)
{
alias Interleave!(Fields!A, FieldNameTuple!A) FieldTypeNameTuple;
}
template CatStruct(A...)
{
alias Tuple!(staticMap!(FieldTypeNameTuple, A)) CatStruct;
}
// usage
struct A { int a; }
struct B { int b; }
struct C { int c; }
alias CatStruct!(A, B, C) ABC;
Run Code Online (Sandbox Code Playgroud)