使用mixin和模板进行结构合成

时间:2015-09-16 18:11:07

标签: templates struct d

我可以撰写一个AB结构,其中包含结构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;

但是,如果我无法控制ABA并且我没有B和{{}},我该如何构建AFields {1}}?即如何编写BFields模板,以便下面的代码编译?

CatStruct

3 个答案:

答案 0 :(得分:9)

标准库中有一些隐藏的珠宝,在我偷看源头回答这个问题之前我实际上并不了解自己:

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

这使用了一个字符串mixin虽然...虽然字符串mixin基本上可以做任何事情,但它们也基本上很糟糕。这可能很脆弱,但我认为它基本上可以在吸吮的同时发挥作用。

它也不会做结构方法,但我认为对于任何这些神奇的东西都很难实现,除了opDispatch之外,如另一个答案中所见(这是非常好的顺便说一下,不要我把我的答案视为对那一个的否定,只是另一个想法。

如果两个结构之间也存在冲突的名称,它们将会破坏它,并且您将从编译器中获得一个可怕的丑陋错误消息。使用真正的模板mixin,可以轻松解决这个问题 - 命名模板mixin,它允许您消除歧义。但这里没有这样的事情。如果你需要它,我想你可以破解它。

但是无论如何,可能有一种方法可以使用stdlib中的那些FieldTypeTupleFieldNameTuple来做得更好,但我认为它或多或少是你所要求的现在

顺便说一句,我会说如果你可以那么只做普通的作文,它会发挥最好的作用。 (不要忘记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

由于这是结构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!它不知道字符串“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; 

编译它并看到一个愚蠢的声音错误:

$ dmd f.d ~/arsd/color.d
f.d(12): Error: cannot implicitly convert expression (white()) of type Color to Color
BTW:记住这一点,如果你在野外看到它 - 编译器错误信息听起来很荒谬,“不能隐式地将Color转换为Color”,但它实际上确实具有逻辑意义:只有两种不同的类型具有相同的名称在不同的模块中。

无论如何,这听起来很愚蠢,但是有道理,因为两个范围导入了不同的结构。

使用与本地静态导入一起使用的长格式FieldTypeTuple,它总是引用传入的实际类型。间接,确定,但也明确。

我向那些已经了解字符串mixins的陷阱的读者道歉,但是在搜索中发现这一点的人可能不知道为什么我使用那些错综复杂的代码。我发誓,由于实际问题的真实世界经验很复杂! :)第一次做正确的事情要比尝试调试奇怪的废话更容易,因为它可以带来另一种方式。

答案 1 :(得分:6)

这里有很多理由(成员,功能,模板等)。 但是,这是一个让你入门的想法:

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,具体取决于哪些代理将被编译。

答案 2 :(得分:5)

为了完整性,这里有一个使用命名元组的解决方案:

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;