PhilippeSigaud/D-templates-tutorial — Issue #3: Another template example
Author: AndrejMitrovic
Created: 2011-12-31 18:47:06+00:00
State: open

Here's another template sample if you'd like to include it, it's similar to that Recorder example but this one fires a delegate when a change happens to a field:

/*
* Copyright Andrej Mitrovic 2011.
* Distributed under the Boost Software License, Version 1.0.
*
* Info: Invokes a custom delegate when a Notify!Type field has changed.
*/
module test;
import std.stdio;

template Fields(T, string field1, string field2)
{
mixin(q{
alias typeof(this) This;
});

mixin("T " ~ field1 ~ ";");
mixin("T " ~ field2 ~ ";");

mixin(q{
This opBinary(string op)(This rhs)
{
This res;
mixin("res." ~ field1 ~ " = this." ~ field1 ~ op ~ " rhs." ~ field1 ~ ";");
mixin("res." ~ field2 ~ " = this." ~ field2 ~ op ~ " rhs." ~ field2 ~ ";");
return res;
}

void opOpAssign(string op)(This rhs)
{
mixin("this = this " ~ op ~ " rhs;");
}
});
}

struct Point {
mixin Fields!(int, "x", "y");
}

struct Size {
mixin Fields!(int, "width", "height");
}

class Widget
{
this()
{
point.init((Point pt) { writefln("changed point to %s", pt); });
size.init((Size sz) { writefln("changed size to %s", sz); });
}

Notify!Point point;
Notify!Size size;
}

struct Notify(T)
{
T payload;
alias void delegate(T) OnEventFunc;
OnEventFunc onEvent;

void init(OnEventFunc func) {
onEvent = func;
}

static if (is(T.toString)) {
alias T.toString toString;
}

auto opEquals(T)(T rhs)
{
return rhs == rhs;
}

void opAssign(T)(T rhs)
{
auto temp = payload;
payload = rhs;

if (temp != payload)
onEvent(payload);
}

void opOpAssign(string op, T)(T rhs)
{
auto temp = payload;
mixin("payload " ~ op ~ "= rhs;");

if (temp != payload)
onEvent(payload);
}
}

void main()
{
auto widget = new Widget;
widget.point = Point(1, 10); // changed point, delegate invoked
assert(widget.point == Point(1, 10));

widget.size += Size(1, 10); // changed size, delegate invoked
assert(widget.size == Size(1, 10));

widget.point += Point(0, 0); // no change, delegate not invoked
widget.size += Size(0, 0); // no change, delegate not invoked
assert(widget.point == Point(1, 10));
assert(widget.size == Size(1, 10));
}

If there was a way to use `static foreach` then Fields could be improved to take any number of arguments. Another trick might be to use `__traits(allMembers, Type);` to extract fields and assign each field of a type this way.