【问题标题】:Struct composition with mixin and templates使用 mixin 和模板进行结构组合
【发布时间】:2015-09-16 18:11:07
【问题描述】:

我可以编写一个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 并且我没有AFieldsBFields,我该如何构造AB? IE。如何编写CatStruct模板以便下面的代码编译?

struct A { int a; }
struct B { int b; }
mixin CatStruct!("AB", A, B);
AB ab;
ab.a = 1; ab.b = 2;

【问题讨论】:

  • 我想你可以在编译时读取所有结构的成员并组成一个新类型。见__traits,尤其是allMembers
  • 直到编译器支持多个alias this rcorre 的解决方案是次优的。

标签: templates struct d


【解决方案1】:

标准库有一些隐藏的宝石,直到我偷看源来回答这个问题之前,我什至都不了解自己:

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,正如在另一个答案中看到的那样(顺便说一句,这很好,不要把我的回答当作对那个答案的否定,只是另一个想法)。

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

但无论如何,可能有一种方法可以使用 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 

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

顺便说一句:如果您在野外看到它,请记住这一点 - 编译器错误消息听起来很荒谬,“无法将颜色隐式转换为颜色”,但它确实具有逻辑含义:只有两种不同的类型具有相同的名称在不同的模块中。

不管怎样,这听起来很傻,但有道理,因为两个作用域导入了不同的结构。

对于与本地静态导入一起使用的长格式 FieldTypeTuple,它始终指代传入的实际类型。间接地,当然,但也很明确。

我向那些已经知道字符串混合的陷阱的阅读本文的人道歉,但是任何在搜索中发现它的人可能不知道我为什么使用那个令人费解的代码。我发誓,由于现实世界的实际问题经验,这很复杂! :) 第一次就做对比尝试调试奇怪的废话要容易得多。

【讨论】:

  • 我实际上将该错误消息作为增强请求提交issues.dlang.org/show_bug.cgi?id=15077,希望我们可以使该消息本身对未来从未见过它的人更有用。但在它被改变之前,把这个模式和解释记录在你的脑海里,这样你就知道了!
  • 不错!它具有直接拥有成员的好处,因此进一步组合这些结构仍然很简单。例如。 writeln(ab) 是干净的。 +100 用于处理可能未知的类型!谢谢!
  • format 现在在编译时工作;这有助于提高字符串混合的可读性。
【解决方案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

【讨论】:

  • 非常优雅!谢谢!
【解决方案3】:

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

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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多