Looks like I can use some D tricks for this: import std.stdio;
struct Point { int x, y; void opOpAssign(string op)(int rhs) { mixin ("x = x " ~ op ~ " rhs;"); mixin ("y = y " ~ op ~ " rhs;"); } } struct Wrapped(T) { T payload; alias payload this; void delegate() dg; @property void changed(void delegate() dg) { this.dg = dg; } void opOpAssign(string op)(int rhs) { payload.opOpAssign!op(rhs); dg(); } } struct Wrapper { this(int x) { point.changed = ¬ifyChanged; } void notifyChanged() { writeln("changed!"); } public Wrapped!Point point; } void main() { auto wrap = Wrapper(1); wrap.point = Point(1, 1); assert(wrap.point == Point(1, 1)); wrap.point += 1; assert(wrap.point == Point(2, 2)); } Pretty cool. I might even be able to write a Wrapped() template that searches for all operator overloads of a type and creates forwarding functions.