On Thursday, 27 December 2012 at 21:21:24 UTC, Philippe Sigaud
wrote:
On Thu, Dec 27, 2012 at 9:01 PM, comco
<[email protected]> wrote:
I wrote a simple template mixin to hold common operator
overloads:
mixin template VectorSpaceOpsMixin(Vector, Scalar, alias a,
alias b)
{
Vector opUnary(string op)() if (op == "-") {
return Vector(-a, -b);
}
...
}
This works like this:
struct complex {
double a, b;
mixin VectorSpaceOpsMixin!(complex, double, a, b);
...
}
Now this is nice, but it only works when the vector has 2
parts. Can the
mixin template be made more general? What will then be its
definition? This
don't work:
mixin template `VectorSpaceOpsMixin(Vector, Scalar, alias...
parts) { ...
}`
First, the `Vector` part is redundant. You can use
`typeof(this)` inside
your mixin to have it 'look around` when it's being mixed in
and determine
the type of the local `this`.
So you mixin can become:
mixin VectorSpaceOpsMixin!(double, a, b);
The `double` part could be deduced also, but it depends what
kind of layout
you envision for your host structs. I'll let it there.
Now, concerning your question, you can make `parts` a template
tuple
parameter:
VectorSpaceOpsMixin(Vector, Scalar, args...) {
The trouble is then that
mixin VectorSpaceOpsMixin!(complex, double, a, b);
will have `args` be the tuple (a,b) (it contains the symbols,
not directly
the value themselves). Tuples are not arithmetic values in D
(that would
not make sense for most tuples), so you cannot do `-args`
directly.
From there, it depends whether you really want the flexibility
that passing
a and b around gives you, or if you can make some simplifying
assumptions
concerning your host struct.
Solution #1: full flexibility: you want to keep the capability
to name the
fields upon which the mixin will act.
That way, even with
struct Vector { double a,b,c; }
you can have
mixin VectorSpaceOpsMixin!(Scalar, a,b); // Look Ma, no c!
You can iterate on `args`. By using `.stringof` on its
elements, you get
"this.a", "this.b", ... From that, you can create the wanted
code. Here is
a possibility:
mixin template VectorSpaceOpsMixin(Scalar, args...)
{
typeof(this) opUnary(string op)() if (op == "-") {
typeof(this) temp;
// "temp.a = -this.a;"...
foreach(index, arg; args)
mixin("temp" ~ args[index].stringof[4..$] ~ " = -" ~
args[index].stringof ~ ";");
return temp;
}
}
It creates a temporary, but then a direct return solution would
also have
to make one. The only thing I'm not sure is when using floating
point
types: `temp.a` and `temp.b` are all initialized with NaN. I
don't know if
it's slow to assign another floating point to a NaN. I guess a
one line
solution is doable, with a bit of string mixin magic.
Btw, of course, there will be many duplication between the
different
arithmetic operators. You can also write another template to,
er, write
your mixin for you. But I personally wouldn't bother: it's
easier to
maintain explicit code.
Solution # 2: the mixin will act on all fields inside the
struct => no need
to pass the names around. In this case, I would even use the
first field as
the type of Scalar.
Everything is greatly simplified:
mixin template VectorSpaceOpsMixin2() // no args!
{
typeof(this) opUnary(string op)() if (op == "-") {
typeof(this) temp;
foreach(index, ref arg; temp.tupleof)
arg = -this.tupleof[index];
return temp;
}
}
struct Complex {
double a, b;
mixin VectorSpaceOpsMixin2;
}
Don't worry about the foreach: it's all unrolled at
compile-time.
I've myself written something like the first, but the second
approach suits my needs better. typeof(this) is very nice. I
intend to use it (among other things) for multivectors, so the
scalar type is not directly obtainable from the parts, but then,
I can allow partwise multiplication with whatever type the parts
may multiply. The nicest thing about the two approaches is that
they are orthogonal - a single template mixin may implement them
both!