问题
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