Struct composition with mixin and templates

别说谁变了你拦得住时间么 提交于 2019-12-23 12:16:05

问题


I can compose an AB struct that has all the members of structs A and 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;

But how can I construct AB, if I don't have control over A and B and I don't have AFields and BFields? I.e. how to write the CatStruct template so the code below compiles?

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

回答1:


There's a lot of ground to cover here (members, functions, templates, ect.). However, here's an idea to get you started:

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

I haven't had time to thoroughly test this, so I wouldn't be suprised if there are a number of areas where it falls over (just look at the implementation of Proxy to see all the things it has to take into account).

However, the general idea is to create two proxies, each explicitly named (aProxy,bProxy) so we can explicitly call the opDispatch of either one depending on which will compile.




回答2:


The standard library has a few hidden jewels that I didn't actually even know about myself until I peeked at the source to answer this question:

http://dlang.org/phobos/std_traits.html#Fields

and the ones right under it too. With these, we can make your CatStruct fairly succinctly. Behold:

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

This uses a string mixin though... and while string mixins can do basically anything, they also basically suck. This is liable to be brittle but I think it will basically work while basically sucking.

It also won't do struct methods, but I think that's too hard to realistically do with any of these magical things, except perhaps opDispatch, as seen in the other answer (which is pretty nice btw, don't take my answer as a repudiation of that one, just another idea).

If there's clashing names between the two structs too, they will break this, and you will get a hideously ugly error message out of the compiler. With a real template mixin, there's an easy fix for that - a named template mixin, which allows you to disambiguate. But no such thing here. I guess you could hack one in if you needed it.

But anyway, there might be a way to use those FieldTypeTuple and FieldNameTuple from the stdlib to do this even nicer, but I think it is more-or-less what you're asking for now.

BTW, I'd say just do ordinary composition if you at all can, it is going to work the best in general. (Don't forget about alias this too which can do automatic forwarding to member variables.)


If you haven't done a lot of mixins, you probably want to ask my why I used that crazy string in the code ~= part instead of the more straightforward. code ~= field.stringof ~ " "~ FieldNameTuple!t[idx] ~ ";";

tl;dr: just trust me, ALWAYS use local names available to the scope where you run the mixin() itself in the code you generate. Long explanation follows/

It has to do with name clashes and symbol lookups. I used static imports and fully qualified names in the mixed in code - including using the local symbol for the FieldTypeTuple rather than field.stringof - to keep this as namespace-tidy as possible.

Consider the case where struct A imports some other module internally and defines a field with it.

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

Since that's a local import inside struct A, the name is meaningless at the point of the mixin.

Go ahead and adjust the mixin so it compiles using the simple line

                // 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] ~ ";";

And compile:

$ 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! It had no idea what the string "Color" was supposed to refer to. If we imported some other kind of struct Color in the local module, it would compile.... but then it would refer to a different type:

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; 

Compile it and see a silly sounding error:

$ dmd f.d ~/arsd/color.d
f.d(12): Error: cannot implicitly convert expression (white()) of type Color to Color

BTW: remember this if you ever see it in the wild - the compiler error message sounds absurd, "cannot implicitly convert Color to Color", but it actually does have a logical meaning: there's just two different types with the same name in different modules.

Anyway, it sounds silly, but makes sense because the two scopes imported different structs.

With the long-form FieldTypeTuple used with a local static import, it always refers to the actual type passed in. Indirectly, sure, but also unambiguously.

I apologize to those reading this who already know about the pitfalls of string mixins, but anyone finding this on a search might not know why I used that convoluted code. It is complex due to real world experience with actual problems, I swear! :) It is a lot easier to do it right the first time than try to debug the weird nonsense down the road it can bring doing it the other way.




回答3:


In the interest of completeness, here's a solution that uses named tuples:

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;


来源:https://stackoverflow.com/questions/32615733/struct-composition-with-mixin-and-templates

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!