Templates 具有mixin和模板的结构组合
我可以组成一个包含结构Templates 具有mixin和模板的结构组合,templates,struct,d,Templates,Struct,D,我可以组成一个包含结构A和B所有成员的AB结构: 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; 但是,如果我不能控制A和B
A
和B
所有成员的AB
结构:
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;
但是,如果我不能控制A
和B
并且我没有A字段和B字段,我如何构建AB
?也就是说,如何编写CatStruct
模板以编译下面的代码
struct A { int a; }
struct B { int b; }
mixin CatStruct!("AB", A, B);
AB ab;
ab.a = 1; ab.b = 2;
这里有很多内容(成员、函数、模板等)。
然而,这里有一个让您开始的想法:
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);
}
我还没有时间彻底测试它,所以如果有很多方面需要考虑,我也不会感到惊讶(只要看看Proxy
的实现,就可以看到它必须考虑的所有方面)
但是,一般的想法是创建两个代理,每个代理都显式命名(aProxy,bProxy),这样我们就可以显式调用其中一个代理的opDispatch
,具体取决于哪个代理将编译。标准库中有一些隐藏的宝石,在我查看源代码回答这个问题之前,我甚至不知道自己:
还有就在它下面的那些。有了这些,我们可以使您的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());
}
不过,这使用了字符串混音。。。虽然字符串混合器基本上可以做任何事情,但它们也基本上很糟糕。这是容易脆,但我认为它将基本上工作,而基本上吸吮
它也不会使用结构化方法,但我认为这对于任何这些神奇的东西来说都太难了,除了在另一个答案中看到的opDispatch
(顺便说一句,这很好,不要把我的答案看作是对那一个的否定,这只是另一个想法)
如果两个结构之间的名称也发生冲突,它们将破坏这一点,您将从编译器中得到一条可怕的错误消息。对于真正的模板混合,有一个简单的修复方法-命名模板混合,它允许您消除歧义。但这里没有这样的事。如果你需要的话,我想你可以插一个进去
但无论如何,可能有一种方法可以使用stdlib中的FieldTypeTuple
和FieldNameTuple
来更好地实现这一点,但我认为这或多或少是您现在想要的
顺便说一句,我想说,如果你能做的话,就做普通的作文吧,一般来说,它会做得最好。(不要忘记别名this
,它可以自动转发到成员变量。)
如果你没有做过很多混音,你可能想问我为什么我在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
因为这是结构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] ~ ";";
并汇编:
$ 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
Zoinks!它不知道字符串“颜色”指的是什么。如果我们在本地模块中导入其他类型的结构颜色,它将编译。。。。但它会指的是另一种类型:
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;
编译后会看到一个听起来很愚蠢的错误:
$ dmd f.d ~/arsd/color.d
f.d(12): Error: cannot implicitly convert expression (white()) of type Color to Color
顺便说一句:如果你在野外看到它,请记住这一点——编译器错误消息听起来很荒谬,“不能隐式地将颜色转换为颜色”,但它实际上有一个逻辑意义:在不同的模块中只有两种不同的类型具有相同的名称
无论如何,这听起来很傻,但有道理,因为这两个作用域导入了不同的结构
对于本地静态导入使用的长格式FieldTypeTuple
,它总是指传入的实际类型。间接地,肯定地,但也毫不含糊地
我向那些已经知道字符串混合的陷阱的读者道歉,但是任何在搜索中发现这一点的人可能都不知道我为什么使用那个复杂的代码。这是复杂的,因为现实世界的经验与实际问题,我发誓!:)第一次做对要比用另一种方法调试奇怪的废话容易得多。为了完整性起见,这里有一个使用命名元组的解决方案:
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;
我想您可以在编译时读取所有结构的成员,然后编写一个新类型。请特别参阅。在编译器支持多个别名之前,此rcorre的解决方案是次好的选择。我实际上将该错误消息作为增强请求提交,希望我们能够使该消息本身对将来从未见过它的人更有用。但是,在它被改变之前,把这个模式和解释存档在你的脑海里,这样你就知道了!美好的它有直接拥有成员的好处,因此组成这些结构更简单。例如,writeln(ab)是干净的+100用于处理可能未知的类型!谢谢<代码>格式
现在在编译时工作;这有助于提高字符串混合的可读性。非常优雅!非常感谢。