1 /** 2 * This module provides a single type, `SuperStruct`. 3 * 4 * Authors: Ryan Roden-Corrent ($(LINK2 https://github.com/rcorre, rcorre)) 5 * License: MIT 6 * Copyright: © 2015, Ryan Roden-Corrent 7 */ 8 module superstruct; 9 10 /** 11 * It's a `struct`! It's a `class`! No, its ... `SuperStruct`! 12 * 13 * This bastard child of 14 * $(LINK2 http://dlang.org/phobos/std_typecons.html#.wrap, wrap) 15 * and 16 * $(LINK2 http://dlang.org/phobos/std_variant.html, variant) 17 * works like an 18 * `Algebraic`, but exposes members that are common across the source types. 19 */ 20 unittest { 21 import std.math, std.algorithm; 22 23 struct Vector { float x, y; } 24 25 struct Square { 26 float size; 27 Vector topLeft; 28 29 auto area() { return size * size; } 30 31 auto center() { 32 return Vector(topLeft.x + size / 2, topLeft.y + size / 2); 33 } 34 35 auto center(Vector c) { 36 return topLeft = Vector(c.x - size / 2, c.y - size / 2); 37 } 38 } 39 40 struct Circle { 41 float radius; 42 Vector center; 43 float area() { return radius * radius * PI; } 44 } 45 46 // Shape may look like a class, but its actually a struct 47 alias Shape = SuperStruct!(Square, Circle); 48 49 Shape sqr = Square(2, Vector(0,0)); 50 Shape cir = Circle(4, Vector(0,0)); 51 Shape[] shapes = [ sqr, cir ]; 52 53 // call functions that are shared between the source types: 54 assert(shapes.map!(x => x.area).sum.approxEqual(2 * 2 + 4 * 4 * PI)); 55 56 // It doesn't matter that `center` is a field of `Circle`, but a property of Square. 57 // They can be used in the same way: 58 cir.center = Vector(4, 2); 59 sqr.center = cir.center; 60 assert(sqr.center == Vector(4,2)); 61 } 62 63 /// SuperStruct forwards operators too: 64 unittest { 65 import std.range, std.algorithm, std.container; 66 67 alias Container(T) = SuperStruct!(SList!T, Array!T); 68 69 Container!int slist = SList!int(); 70 71 // We can call any members that are common among containers 72 slist.insert([1,2,3,4]); 73 assert(slist.front == 1); 74 75 // opSlice is supported on all the subtypes, but each returns a different type 76 // Container.opSlice will return a SuperStruct of these types 77 auto slice = slist[]; // [1,2,3,4] 78 assert(slice.front == 1); 79 slice.popFront(); // [2,3,4] 80 assert(slice.front == 2); 81 82 // as slice is a SuperStruct of range types, it still works as a range 83 slist.insert(slice); // [2,3,4] ~ [1,2,3,4] 84 assert(slist[].equal([2,3,4,1,2,3,4])); 85 } 86 87 import std.meta; 88 import std.traits; 89 import std.variant; 90 91 /** A Variant which exposes members that are common across all `SubTypes`. 92 * 93 * A `SuperStruct!(SubTypes...)` wraps an `Algebraic!(SubTypes...)`. It can 94 * hold a single value from any of its `SubTypes`. 95 * 96 * Unlike a Variant/Algebraic, `SuperStruct` exposes access to 'common' members 97 * that have compatible signatures. 98 * 99 * A member is 'common' if its name describes a public function or field on 100 * every one of `SubTypes`. A call signature for a given member is 'compatible' 101 * if, for an instance of any one of `SubTypes`, that member can be called with 102 * the provided set of arguments _and_ all such calls have a common return type. 103 * 104 * `SuperStruct` ignores members beginning with "__" (double underscore). 105 */ 106 struct SuperStruct(SubTypes...) { 107 private Algebraic!SubTypes _value; 108 109 /** 110 * Construct and populate with an initial value. 111 * Params: 112 * value = something implicitly covertible to of one of the `SubTypes`. 113 */ 114 this(V)(V value) if (is(typeof(_value = value))) { 115 _value = value; 116 } 117 118 /** 119 * Populate the `SuperStruct` with an instance of one of its sub-types. 120 * Params: 121 * value = something implicitly covertible to of one of the `SubTypes`. 122 */ 123 auto opAssign(V)(V value) if (is(typeof(_value = value))) { 124 return _value = value; 125 } 126 127 /// Forward all members and template instantiations to the contained value. 128 mixin(allVisitorCode!SubTypes); 129 130 // - Basic Operator Forwarding --------------------------------- 131 132 /// Operators are forwarded to the underlying type. 133 auto opIndex(T...)(T t) { return _value.visitAny!(x => x[t]); } 134 135 /// ditto 136 auto opSlice()() { return _value.visitAny!(x => x[]); } 137 138 /// ditto 139 auto opSlice(A, B)(A a, B b) { return _value.visitAny!(x => x[a..b]); } 140 141 /// ditto 142 auto ref opDollar(size_t pos)() { 143 auto ref dollar(T)(T val) { 144 static if (isArray!T) 145 return val.length; 146 else static if (is(typeof(val.opDollar!pos))) 147 return val.opDollar!pos(); 148 else 149 return val.opDollar(); 150 } 151 152 return _value.visitAny!(x => dollar(x)); 153 } 154 155 /// ditto 156 auto opUnary(string op)() { return _value.visitAny!(x => mixin(op~"x")); } 157 158 /// ditto 159 auto opBinary(string op, T)(T other) { 160 return _value.visitAny!(x => mixin("x"~op~"other")); 161 } 162 163 /// ditto 164 auto opOpAssign(string op, T)(T other) { 165 return _value.visitAny!((ref x) => mixin("x"~op~"=other")); 166 } 167 168 /// ditto 169 bool opEquals(T)(T other) { return _value.visitAny!(x => x == other); } 170 171 /// ditto 172 auto opCmp(T)(T other) { return _value.visitAny!(x => x.opCmp(other)); } 173 174 /// ditto 175 auto opCall(T...)(auto ref T args) { return _value.visitAny!(x => x(args)); } 176 177 // - Operator Forwarding between SuperStructs ----------------- 178 179 /** 180 * Perform a binary operation between two superstructs. 181 * 182 * Only possible if such an operation is supported between any of the types 183 * in either of the SuperStructs. 184 */ 185 auto opBinary(string op, T : SuperStruct!V, V...)(T other) { 186 return _value.visitAny!(x => 187 other._value.visitAny!(y => mixin("x"~op~"y"))); 188 } 189 190 /// ditto 191 auto opOpAssign(string op, T : SuperStruct!V, V...)(T other) { 192 return _value.visitAny!((ref x) => 193 other._value.visitAny!(( y) => mixin("x"~op~"=y"))); 194 } 195 196 /** 197 * Compare one `SuperStruct` to another of the same type. 198 * 199 * Invokes opEquals if the contained types are comparable. 200 * Otherwise returns false. 201 */ 202 auto opEquals(typeof(this) other) { 203 bool helper(A, B)(A a, B b) { 204 static if (is(typeof(a == b))) 205 return a == b; 206 else 207 return false; 208 } 209 210 return _value.visitAny!(x => 211 other._value.visitAny!(y => helper(x, y))); 212 } 213 214 /** 215 * Extract the wrapped value of type `T` from the `SuperStruct`. 216 * Throws a `VariantException` if conversion to `T` is not possible. 217 */ 218 auto opCast(T)() if (staticIndexOf!(T, SubTypes) >= 0) { 219 return _value.get!T; 220 } 221 } 222 223 /// If all types have a matching field, it gets exposed: 224 unittest { 225 struct Foo { int a; } 226 struct Bar { int a; } 227 auto foobar = SuperStruct!(Foo, Bar)(Foo(1)); 228 foobar.a = 5; 229 assert(foobar.a == 5); 230 } 231 232 /// If all types have a matching method, all compatible overloads are exposed: 233 unittest { 234 struct Foo { 235 int fun(int i) { return i; } 236 int fun(int a, int b) { return a + b; } 237 } 238 struct Bar { 239 int fun(int i) { return i; } 240 int fun(int a, int b) { return a + b; } 241 int fun(int a, int b, int c) { return a + b + c; } 242 } 243 244 auto foobar = SuperStruct!(Foo, Bar)(Foo()); 245 assert(foobar.fun(1) == 1); 246 assert(foobar.fun(1, 2) == 3); 247 assert(!__traits(compiles, foobar.fun(1,2,3))); // no such overload on Foo 248 } 249 250 /// If a name is a field on one type and a method on another, it is exposed: 251 unittest { 252 struct Foo { int a; } 253 struct Bar { 254 private int _a; 255 int a() { return _a; } 256 int a(int val) { return _a = val; } 257 } 258 259 auto foo = SuperStruct!(Foo, Bar)(Foo()); 260 foo.a = 5; // sets Foo.a 261 assert(foo.a == 5); // gets Foo.a 262 263 auto bar = SuperStruct!(Foo, Bar)(Bar()); 264 bar.a = 5; // invokes Bar.a(int val) 265 assert(bar.a == 5); // invokes Bar.a() 266 } 267 268 /// Templated members can be forwarded too: 269 unittest { 270 struct Foo { 271 int val; 272 auto transmorgrify(alias fn1, alias fn2)() { 273 return fn2(fn2(val)); 274 } 275 } 276 277 struct Bar { 278 auto transmorgrify(alias fn1, alias fn2)() { return 0; } 279 } 280 281 static auto add1 = (int a) => a + 1; 282 283 alias FooBar = SuperStruct!(Foo, Bar); 284 285 FooBar f = Foo(3); 286 assert(f.transmorgrify!(add1, add1) == 5); // 3 + 1 + 1 287 288 FooBar b = Bar(); 289 assert(b.transmorgrify!(add1, add1) == 0); 290 } 291 292 /// Operators get forwarded to the underlying type 293 unittest { 294 struct Foo { 295 auto opSlice() { return [1,2,3]; } 296 } 297 298 struct Bar { 299 auto opSlice() { return [4,5,6]; } 300 } 301 302 SuperStruct!(Foo, Bar) fb = Foo(); 303 assert(fb[] == [1,2,3]); 304 } 305 306 /// SuperStructs of the same type can be compared: 307 unittest { 308 struct A { int i; } 309 struct B { int i; } 310 struct C { int i; bool opEquals(T)(T other) { return other.i == i; } } 311 312 SuperStruct!(A, B, C) a0 = A(0); 313 SuperStruct!(A, B, C) a1 = A(1); 314 SuperStruct!(A, B, C) b0 = B(0); 315 SuperStruct!(A, B, C) c0 = C(0); 316 317 assert(a0 == a0); // same type, same value 318 assert(a0 != a1); // same type, different value 319 assert(a0 != b0); // incomparable types return false 320 assert(a0 == c0); // different but comparable types 321 322 // SuperStructs with different sets of source types are not comparable, even 323 // if the types they happen to contain at the moment are. 324 SuperStruct!(A, B) different = A(0); 325 static assert(!__traits(compiles, different == a0)); 326 } 327 328 /// If members have common signatures but no common return type, the exposed 329 /// member returns a `SuperStruct` of the possible return types. 330 unittest { 331 struct A { auto fun() { return 1; } } 332 struct B { auto fun() { return "hi"; } } 333 334 SuperStruct!(A, B) a = A(); 335 SuperStruct!(A, B) b = B(); 336 337 assert(a.fun == SuperStruct!(int, string)(1)); 338 assert(b.fun == SuperStruct!(int, string)("hi")); 339 } 340 341 /// Use `cast(T)` to convert a `SuperStruct` to a subtype 342 unittest { 343 import std.exception : assertThrown; 344 345 struct Square { float size; } 346 struct Circle { float r; } 347 348 SuperStruct!(Square, Circle) sqr = Square(4); 349 SuperStruct!(Square, Circle) cir = Circle(6); 350 351 assert((cast(Square) sqr).size == 4); 352 assert((cast(Circle) cir).r == 6); 353 354 assertThrown!VariantException(cast(Circle) sqr); 355 assertThrown!VariantException(cast(Square) cir); 356 } 357 358 /** 359 * Wrap one of two values in a `SuperStruct` that encompasses both types. 360 * 361 * This can be used to return a value from one of several different types. 362 * Similar to `std.range.chooseAmong`, but for a broader range of types. 363 * 364 * Params: 365 * condition = which value to choose: $(D a) if $(D true), $(D b) otherwise 366 * a = the "true" value 367 * b = the "false" value 368 * 369 * Returns: A `SuperStruct!(T1,T2)` wrapping the selected value. 370 */ 371 auto pick(T1, T2)(bool condition, T1 a, T2 b) { 372 if (condition) return SuperStruct!(T1, T2)(a); 373 else return SuperStruct!(T1, T2)(b); 374 } 375 376 /// 377 unittest { 378 struct Square { int size; int area() { return size * size; } } 379 struct Rect { int w ,h; int area() { return w * h; } } 380 381 auto shape(int w, int h) { 382 return pick(w == h, Square(w), Rect(w,h)); 383 } 384 385 assert(shape(4,5).area == 20); 386 assert(shape(3,3).area == 9); 387 } 388 389 /// `pick` is useful for something that is a floor wax _and_ a dessert topping: 390 unittest { 391 struct FloorWax { string itIs() { return "a floor wax!"; } } 392 struct DessertTopping { string itIs() { return "a dessert topping!"; } } 393 394 auto shimmer(bool hungry) { 395 return pick(hungry, DessertTopping(), FloorWax()); 396 } 397 398 assert(shimmer(false).itIs == "a floor wax!"); 399 assert(shimmer(true ).itIs == "a dessert topping!"); 400 } 401 402 /** 403 * Wrap one of two values in a `SuperStruct` that encompasses both types. 404 * 405 * This can be used to return a value from one of several different types. 406 * Similar to `std.range.chooseAmong`, but for a broader range of types. 407 * 408 * Params: 409 * index = which value to choose, must be less than the number of values 410 * values = two or more values to pick from 411 * 412 * Returns: A `SuperStruct!T` wrapping the argument at `index`. 413 */ 414 auto pickAmong(T...)(size_t index, T values) { 415 foreach(idx, val ; values) 416 if (idx == index) 417 return SuperStruct!T(val); 418 419 assert(0, "index not in range of provided values"); 420 } 421 422 /// use `pickAmong` to form a common return type from different structs 423 unittest { 424 struct Zero { auto num = 0; } 425 struct One { auto num = 1; } 426 struct Two { auto num = 2; } 427 428 auto val = pickAmong(0, Zero(), One(), Two()); 429 assert(val.num == 0); 430 431 val = pickAmong(1, Zero(), One(), Two()); 432 assert(val.num == 1); 433 434 val = pickAmong(2, Zero(), One(), Two()); 435 assert(val.num == 2); 436 } 437 438 /// `pickAmong` is similar to `std.range.chooseAmong`: 439 unittest { 440 import std.range : only, iota, repeat; 441 import std.algorithm : equal; 442 443 auto r = pickAmong(0, only(1), iota(0,3), repeat(1,3)); 444 assert(r.equal([1])); 445 446 r = pickAmong(1, only(1), iota(0,3), repeat(1,3)); 447 assert(r.equal([0,1,2])); 448 449 r = pickAmong(2, only(1), iota(0,3), repeat(1,3)); 450 assert(r.equal([1,1,1])); 451 } 452 453 private: 454 /* 455 * Try to invoke `member` with the provided `args` for every `AllowedType`. 456 * Compiles only if such a call is possible on every type. 457 * Compiles only if the return values of all such calls share a common type. 458 */ 459 template visitMember(string member, TemplateArgs...) { 460 // if we got some explicit compile time args, we need to pass them along. 461 // otherwise, omit the !() as an empty template arg list can cause issues 462 static if (TemplateArgs.length) 463 enum bang = "!(TemplateArgs)"; 464 else 465 enum bang = ""; 466 467 // this nested function allows two sets of compile-time args: 468 // one from the enclosing template scope and one for the variadic args of 469 auto visitMember(V, Args...)(ref V var, Args args) { 470 static if (Args.length == 0) // field or 'getter' (no-args function) 471 enum expression = "x."~member~bang; 472 else static if (Args.length == 1) // field or 'setter' (1-arg function) 473 enum expression = "x."~member~bang~"=args[0]"; 474 else // 2+ arg function 475 enum expression = "x."~member~bang~"(args)"; 476 477 return var.visitAny!((ref x) => mixin(expression)); 478 } 479 } 480 481 unittest { 482 struct Foo { 483 int num; 484 485 string shout() { return "hi!"; } 486 void assign(int val) { num = val; } 487 void assign(int val1, int val2) { num = val1 + val2; } 488 489 void set(T)(T val) { num = val; } // templated setter 490 } 491 492 struct Bar { 493 int num, othernum; 494 495 string shout() { return "bye!"; } 496 void assign(int val) { num = val + 1; } 497 void assign(int val1, int val2) { num = val1 + val2 + 1; } 498 void assign(int val1, int val2, int val3) { num = val1 + val2 + val3; } 499 500 void set(T)(T val) { num = val; } 501 } 502 503 alias Thing = Algebraic!(Foo, Bar); 504 505 Thing foo = Foo(4); 506 Thing bar = Bar(5, 6); 507 508 assert(visitMember!"num"(foo) == 4); 509 assert(visitMember!"num"(bar) == 5); 510 511 assert(visitMember!"num"(foo, 5) == 5); 512 assert(visitMember!"num"(foo) == 5); 513 514 assert(visitMember!"shout"(foo) == "hi!"); 515 assert(visitMember!"shout"(bar) == "bye!"); 516 assert(visitMember!"shout"(bar) == "bye!"); 517 518 visitMember!"assign"(foo, 2); 519 assert(visitMember!"num"(foo) == 2); 520 521 visitMember!"assign"(bar, 2); 522 assert(visitMember!"num"(bar) == 3); // bar adds 1 523 524 visitMember!"assign"(foo, 2, 6); 525 assert(visitMember!"num"(foo) == 8); 526 527 visitMember!"set"(foo, 9); 528 assert(visitMember!"num"(foo) == 9); 529 530 // field 'othernum' only exists on bar 531 static assert(!__traits(compiles, visitMember!"othernum"(bar))); 532 static assert(!__traits(compiles, visitMember!"othernum"(bar))); 533 534 // 3-param overload of 'assign' only exists on Bar 535 static assert(!__traits(compiles, visitMember!"assign"(bar, 2, 6, 8))); 536 } 537 538 // pass along template arguments 539 unittest { 540 struct Foo { 541 int val; 542 543 auto noargs(alias fn)() { return fn(val); } 544 auto onearg(alias fn)(int i) { return fn(i); } 545 auto twofns(alias fn1, alias fn2)(int i) { return fn2(fn1(i)); } 546 547 auto onetype(T)(T arg) { return val + arg; } 548 auto twotype(T, V)(T t, V v) { return val + t + v; } 549 } 550 551 struct Bar { 552 int val; 553 554 auto noargs(alias fn)() { return fn(val); } 555 auto onearg(alias fn)(int i) { return fn(i); } 556 auto twofns(alias fn1, alias fn2)(int i) { return fn2(fn1(i)); } 557 558 auto onetype(T)(T arg) { return val + arg; } 559 auto twotype(T, V)(T t, V v) { return val + t + v; } 560 } 561 562 alias FooBar = Algebraic!(Foo, Bar); 563 FooBar fb = Foo(3); 564 565 // need to use a static fn here due to unrelated issue: 566 // cannot use local 'add1' as parameter to non-global template 567 static auto add1 = (int a) => a + 1; 568 static auto add2 = (int a) => a + 2; 569 570 assert(fb.visitMember!("noargs", add1)() == 4); // 3 + 1 571 assert(fb.visitMember!("onearg", add1)(5) == 6); // 5 + 1 572 assert(fb.visitMember!("twofns", add1, add2)(5) == 8); // 5 + 1 + 2 573 574 // implicit type args 575 assert(fb.visitMember!("onetype")(5) == 8); // 3 + 5 576 assert(fb.visitMember!("twotype")(5, 7) == 15); // 3 + 5 + 7 577 assert(fb.visitMember!("twotype")(5f, 7f) == 15f); // 3 + 5 + 7 578 579 // explicit type args 580 assert(fb.visitMember!("onetype", int)(5) == 8); // 3 + 5 581 assert(fb.visitMember!("twotype", int)(5, 7) == 15); // 3 + 5 + 7 582 assert(fb.visitMember!("twotype", float, float)(5, 7) == 15f); // 3 + 5 + 7 583 584 // only specify some type args 585 assert(fb.visitMember!("twotype", float)(5, 7) == 15f); // 3 + 5 + 7 586 } 587 588 auto visitAny(alias fn, V)(ref V var) { 589 // Collect the possible return types for this function across the subtypes 590 alias returnType(T) = typeof(fn(*var.peek!T)); 591 alias AllTypes = staticMap!(returnType, V.AllowedTypes); 592 593 enum allVoid = EraseAll!(void, AllTypes).length == 0; 594 enum allCommon = !is(CommonType!AllTypes == void); 595 596 foreach(T ; var.AllowedTypes) 597 if (auto ptr = var.peek!T) { 598 static if (allCommon || allVoid) 599 return fn(*ptr); 600 else static if (!allVoid) 601 return SuperStruct!AllTypes(fn(*ptr)); 602 else 603 static assert(0, "Cannot mix void and non-void return types"); 604 } 605 606 assert(0, "Variant holds no value"); 607 } 608 609 unittest { 610 struct Foo { 611 auto opSlice() { return [1,2,3]; } 612 auto opBinary(string op)(string val) { return "foo"~op~"val"; } 613 } 614 615 struct Bar { 616 auto opSlice() { return [4,5,6]; } 617 auto opBinary(string op)(string val) { return "foo"~op~"val"; } 618 } 619 620 Algebraic!(Foo, Bar) fb = Foo(); 621 assert(fb.visitAny!(x => x[]) == [1,2,3]); 622 } 623 624 unittest { 625 struct One { auto opEquals(int i) { return i == 1; } } 626 struct Two { auto opEquals(int i) { return i == 2; } } 627 628 Algebraic!(One, Two) one = One(); 629 Algebraic!(One, Two) two = Two(); 630 631 assert( one.visitAny!(x => x == 1)); 632 assert(!one.visitAny!(x => x == 2)); 633 assert(!two.visitAny!(x => x == 1)); 634 assert( two.visitAny!(x => x == 2)); 635 } 636 637 /* 638 * Generate a templated function to expose access to a given member across all 639 * types that could be stored in the Variant `_value`. 640 * For any given call signature, this template will instantiate only if the 641 * matching member on every subtype is callable with such a signature _and_ if 642 * all such calls have a common return type. 643 */ 644 string memberVisitorCode(string name)() { 645 import std.string : format; 646 647 return q{ 648 template %s(TemplateArgs...) { 649 auto %s(Args...)(Args args) { 650 return visitMember!("%s", TemplateArgs)(_value, args); 651 } 652 } 653 }.format(name, name, name); 654 } 655 656 unittest { 657 struct Foo { 658 int a; 659 int b; 660 int c; 661 int d; 662 int e; 663 } 664 665 struct Bar { 666 int a; 667 real b; 668 int c() { return 1; } // getter only 669 string d; // incompatible type 670 int e(int val) { return 0; } // setter only 671 } 672 673 struct FooBar { 674 alias Store = Algebraic!(Foo, Bar); 675 Store _value; 676 677 this(T)(T t) { _value = t; } 678 679 mixin(memberVisitorCode!("a")); 680 mixin(memberVisitorCode!("b")); 681 mixin(memberVisitorCode!("c")); 682 mixin(memberVisitorCode!("d")); 683 mixin(memberVisitorCode!("e")); 684 } 685 686 FooBar fb = Foo(1); 687 688 static assert(is(typeof(fb.a()) == int)); // both are int 689 static assert(is(typeof(fb.b()) == real)); // real is common type of (int, real) 690 static assert(is(typeof(fb.c()) == int)); // field on Foo, function on Bar 691 692 static assert( is(typeof(fb.a = 5) == int )); // both are int 693 static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real) 694 static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar 695 696 static assert(!is(typeof(fb.b = 5.0))); // type mismatch 697 static assert(!is(typeof(fb.c = 5 ))); // getter only 698 static assert(!is(typeof(fb.d = 5 ))); // incompatible types 699 static assert(!is(typeof(fb.d = 5.0))); // incompatible types 700 } 701 702 /* 703 * Generate a string containing the `memberVisitorCode` for every name in the 704 * union of all members across SubTypes. 705 */ 706 string allVisitorCode(SubTypes...)() { 707 // allMembers will fail on a primitive types, so alias that to an empty list 708 template allMembers(T) { 709 static if (__traits(compiles, __traits(allMembers, T))) 710 alias allMembers = AliasSeq!(__traits(allMembers, T)); 711 else 712 alias allMembers = AliasSeq!(); 713 } 714 715 // ignore hidden members like __ctor, this, and operators 716 enum shouldExpose(string name) = (name.length < 2 || name[0..2] != "__") && 717 name != "this" && 718 name != "opUnary" && 719 name != "opBinary" && 720 name != "opCast" && 721 name != "opEquals" && 722 name != "opCmp" && 723 name != "opCall" && 724 name != "opAssign" && 725 name != "opIndex" && 726 name != "opSlice" && 727 name != "opDollar"; 728 729 // we _could_ generate members that will never instantiate, but might as well 730 // filter out ones that we _know_ don't exist on at least one subtype 731 template allHave(string member) { 732 enum have(T) = hasMember!(T, member); 733 enum allHave = allSatisfy!(have, SubTypes); 734 } 735 736 string str; 737 738 // generate a member to forward to each underlying member 739 foreach(member ; NoDuplicates!(staticMap!(allMembers, SubTypes))) 740 static if (shouldExpose!member && allHave!member) 741 str ~= memberVisitorCode!(member); 742 743 return str; 744 } 745 746 unittest { 747 struct Foo { 748 int a; 749 int b; 750 int c; 751 int d; 752 int e; 753 } 754 755 struct Bar { 756 int a; 757 real b; 758 int c() { return 1; } // getter only 759 string d; // incompatible type 760 int e(int val) { return 0; } // setter only 761 } 762 763 struct FooBar { 764 alias Store = Algebraic!(Foo, Bar); 765 Store _value; 766 767 this(T)(T t) { _value = t; } 768 769 mixin(allVisitorCode!(Foo, Bar)); 770 } 771 772 FooBar fb = Foo(1); 773 774 // getters 775 static assert( is(typeof(fb.a()) == int)); // both are int 776 static assert( is(typeof(fb.b()) == real)); // real is common type of (int, real) 777 static assert( is(typeof(fb.c()) == int)); // field on Foo, function on Bar 778 static assert( is(typeof(fb.d()) == SuperStruct!(int, string))); 779 static assert(!is(typeof(fb.e()))); // setter only 780 781 // setters 782 static assert( is(typeof(fb.a = 5) == int )); // both are int 783 static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real) 784 static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar 785 786 static assert(!is(typeof(fb.b = 5.0))); // type mismatch 787 static assert(!is(typeof(fb.c = 5 ))); // getter only 788 static assert(!is(typeof(fb.d = 5 ))); // incompatible types 789 static assert(!is(typeof(fb.d = 5.0))); // incompatible types 790 } 791 792 // make sure __ctor and __dtor don't blow things up 793 unittest { 794 struct Foo { 795 this(int i) { } 796 this(this) { } 797 ~this() { } 798 } 799 800 struct Bar { 801 this(int i) { } 802 this(this) { } 803 ~this() { } 804 } 805 806 struct FooBar { 807 alias Store = Algebraic!(Foo, Bar); 808 Store _value; 809 810 this(T)(T t) { _value = t; } 811 812 mixin(allVisitorCode!(Foo, Bar)); 813 } 814 815 FooBar fb = Foo(1); 816 }