使用 mixin 和模板的结构组合

Struct composition with mixin and templates

我可以编写一个 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;

但是如果我无法控制 AB 并且我没有 AFields 和 [=19],我该如何构建 AB =]? IE。如何编写 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,具体取决于哪个将编译。

标准库有一些隐藏的宝藏,在我查看源代码回答这个问题之前,我什至都不知道这些宝藏:

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

虽然这使用了字符串混合...虽然字符串混合基本上可以做任何事情,但它们基本上也很糟糕。这很容易变脆,但我认为它基本上可以在吸吮的同时工作。

它也不会执行结构方法,但我认为这些神奇的东西很难现实地处理,除了可能 opDispatch,如另一个答案所示(顺便说一句,这很不错,请不要将我的回答视为对那个的否定,只是另一个想法)。

如果两个结构之间的名称也存在冲突,它们就会破坏这一点,并且您会从编译器中得到一条非常丑陋的错误消息。使用真正的模板混合,有一个简单的解决方法 - 命名模板混合,它允许您消除歧义。但是这里没有这样的东西。我想如果你需要的话,你可以破解一个。

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

顺便说一句,我会说 如果可以的话就做普通的作文,一般来说它会达到最好的效果。 (不要忘记 alias 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,使其使用简单的行进行编译

                // 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 

僵尸!它不知道字符串 "Color" 应该指的是什么。如果我们在本地模块中导入一些 other 类型的 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

顺便说一句:如果你在野外看到它,请记住这一点 - 编译器错误消息听起来很荒谬,"cannot implicitly convert 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;