1 /++
2     This module contains the [meldInto] functions; functions that take two
3     structs or classes of the same type and combine them, creating a resulting
4     object with the union of the members of both parents. Array and associative
5     array variants exist too.
6 
7     Example:
8     ---
9     struct Foo
10     {
11         string abc;
12         string def;
13         int i;
14         float f;
15         double d;
16     }
17 
18     Foo f1; // = new Foo;
19     f1.abc = "ABC";
20     f1.def = "DEF";
21 
22     Foo f2; // = new Foo;
23     f2.abc = "this won't get copied";
24     f2.def = "neither will this";
25     f2.i = 42;
26     f2.f = 3.14f;
27 
28     f2.meldInto(f1);
29 
30     with (f1)
31     {
32         import std.math : isNaN;
33 
34         assert(abc == "ABC");
35         assert(def == "DEF");
36         assert(i == 42);
37         assert(f == 3.14f);
38         assert(d.isNaN);
39     }
40     ---
41 
42     Copyright: [JR](https://github.com/zorael)
43     License: [Boost Software License 1.0](https://www.boost.org/users/license.html)
44 
45     Authors:
46         [JR](https://github.com/zorael)
47  +/
48 module lu.meld;
49 
50 private:
51 
52 import lu.traits : isMerelyArray;
53 import std.traits : isAggregateType, isArray, isAssociativeArray, isMutable;
54 
55 public:
56 
57 
58 /++
59     To what extent a source should overwrite a target when melding.
60  +/
61 enum MeldingStrategy
62 {
63     /++
64         Takes care not to overwrite settings when either the source or the
65         target is `.init`.
66      +/
67     conservative,
68 
69     /++
70         Only considers the `init`-ness of the source, so as not to overwrite
71         things with empty strings, but otherwise always considers the source to
72         trump the target.
73      +/
74     aggressive,
75 
76     /++
77         Works like aggressive but also always overwrites bools, regardless of
78         falseness.
79      +/
80     overwriting,
81 }
82 
83 
84 /++
85     UDA conveying that this member's value cannot or should not be melded.
86  +/
87 enum Unmeldable;
88 
89 
90 // meldInto
91 /++
92     Takes two structs or classes of the same type and melds them together,
93     making the members a union of the two.
94 
95     In the case of classes it only overwrites members in `intoThis` that are
96     `typeof(member).init`, so only unset members get their values overwritten by
97     the melding class. It also does not work with static members.
98 
99     In the case of structs it also overwrites members that still have their
100     default values, in cases where such is applicable.
101 
102     Supply a template parameter [MeldingStrategy] to decide to which extent
103     values are overwritten.
104 
105     Example:
106     ---
107     struct Foo
108     {
109         string abc;
110         int def;
111         bool b = true;
112     }
113 
114     Foo foo, bar;
115     foo.abc = "from foo"
116     foo.b = false;
117     bar.def = 42;
118     foo.meldInto(bar);
119 
120     assert(bar.abc == "from foo");
121     assert(bar.def == 42);
122     assert(!bar.b);  // false overwrote default value true
123     ---
124 
125     Params:
126         strategy = To what extent the source object should overwrite set
127             (non-`init`) values in the receiving object.
128         meldThis = Object to meld (source).
129         intoThis = Reference to object to meld (target).
130  +/
131 void meldInto(MeldingStrategy strategy = MeldingStrategy.conservative, QualThing, Thing)
132     (auto ref QualThing meldThis, ref Thing intoThis)
133 if (isAggregateType!Thing && is(QualThing : Thing) && isMutable!Thing)
134 {
135     static if (is(Thing == struct) && (strategy == MeldingStrategy.conservative))
136     {
137         import std.traits : hasUnsharedAliasing;
138 
139         if (meldThis == Thing.init)
140         {
141             // We're merging an .init with something, and .init does not have
142             // any special default values. Nothing would get melded, so exit early.
143             return;
144         }
145 
146         static if (!hasUnsharedAliasing!Thing)
147         {
148             if (intoThis == Thing.init)
149             {
150                 // Likewise we're merging into an .init, so just fast-path overwrite.
151                 intoThis = meldThis;
152                 return;
153             }
154         }
155     }
156 
157     foreach (immutable i, ref _; intoThis.tupleof)
158     {
159         import std.traits : isType;
160 
161         static if (!isType!(intoThis.tupleof[i]))
162         {
163             import lu.traits : udaIndexOf;
164             import std.traits :
165                 isAggregateType,
166                 isArray,
167                 isAssignable,
168                 isPointer,
169                 isSomeString;
170 
171             alias T = typeof(intoThis.tupleof[i]);
172 
173             static if (udaIndexOf!(intoThis.tupleof[i], Unmeldable) != -1)
174             {
175                 // Do nothing
176             }
177             else static if (isAggregateType!T)
178             {
179                 // Recurse
180                 meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]);
181             }
182             else static if (isAssignable!T)
183             {
184                 /+
185                     Overwriting strategy overwrites everything except where the
186                     source is clearly `.init`.
187 
188                     Aggressive strategy works like overwriting except it doesn't
189                     blindly overwrite struct bools.
190                  +/
191                 static if (
192                     (strategy == MeldingStrategy.overwriting) ||
193                     (strategy == MeldingStrategy.aggressive))
194                 {
195                     static if (is(T == float) || is(T == double))
196                     {
197                         import std.math : isNaN;
198 
199                         if (!meldThis.tupleof[i].isNaN)
200                         {
201                             intoThis.tupleof[i] = meldThis.tupleof[i];
202                         }
203                     }
204                     else static if (is(T == bool))
205                     {
206                         static if (strategy == MeldingStrategy.overwriting)
207                         {
208                             // Non-discriminately overwrite bools
209                             intoThis.tupleof[i] = meldThis.tupleof[i];
210                         }
211                         else static if (strategy == MeldingStrategy.aggressive)
212                         {
213                             static if (is(Thing == class))
214                             {
215                                 // We cannot tell whether or not it has the same value as
216                                 // `Thing.init` does, as it would need to be instantiated.
217                                 // Assume overwrite?
218                                 intoThis.tupleof[i] = meldThis.tupleof[i];
219                             }
220                             else
221                             {
222                                 if (intoThis.tupleof[i] == Thing.init.tupleof[i])
223                                 {
224                                     intoThis.tupleof[i] = meldThis.tupleof[i];
225                                 }
226                             }
227                         }
228                         else
229                         {
230                             static assert(0, "Logic error; unexpected `MeldingStrategy` " ~
231                                 "passed to struct/class `meldInto`");
232                         }
233                     }
234                     else static if (isArray!T && !isSomeString!T)
235                     {
236                         // Pass on to array melder
237                         meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]);
238                     }
239                     else static if (isAssociativeArray!T)
240                     {
241                         // Pass on to AA melder
242                         meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]);
243                     }
244                     else static if (isPointer!T)
245                     {
246                         // Aggressive and/or overwriting, so just overwrite the pointer?
247                         intoThis.tupleof[i] = meldThis.tupleof[i];
248                     }
249                     else static if (is(Thing == class))
250                     {
251                         // Can't compare with Thing.init.tupleof[i]
252                         intoThis.tupleof[i] = meldThis.tupleof[i];
253                     }
254                     else
255                     {
256                         if (meldThis.tupleof[i] != Thing.init.tupleof[i])
257                         {
258                             intoThis.tupleof[i] = meldThis.tupleof[i];
259                         }
260                     }
261                 }
262                 /+
263                     Conservative strategy takes care not to overwrite members
264                     with non-`init` values.
265                  +/
266                 else static if (strategy == MeldingStrategy.conservative)
267                 {
268                     static if (is(T == float) || is(T == double))
269                     {
270                         import std.math : isNaN;
271 
272                         if (intoThis.tupleof[i].isNaN)
273                         {
274                             intoThis.tupleof[i] = meldThis.tupleof[i];
275                         }
276                     }
277                     else static if (is(T == enum))
278                     {
279                         if (meldThis.tupleof[i] > intoThis.tupleof[i])
280                         {
281                             intoThis.tupleof[i] = meldThis.tupleof[i];
282                         }
283                     }
284                     else static if (is(T == string[]))
285                     {
286                         import std.algorithm.searching : canFind;
287 
288                         if (!intoThis.tupleof[i].canFind(meldThis.tupleof[i]))
289                         {
290                             intoThis.tupleof[i] ~= meldThis.tupleof[i];
291                         }
292                     }
293                     else static if (isArray!T && !isSomeString!T)
294                     {
295                         // Pass on to array melder
296                         meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]);
297                     }
298                     else static if (isAssociativeArray!T)
299                     {
300                         // Pass on to AA melder
301                         meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]);
302                     }
303                     else static if (isPointer!T)
304                     {
305                         // Conservative, so check if null and overwrite if so
306                         if (!intoThis.tupleof[i]) intoThis.tupleof[i] = meldThis.tupleof[i];
307                     }
308                     else static if (is(T == bool))
309                     {
310                         static if (is(Thing == class))
311                         {
312                             /+
313                                 We cannot tell whether or not it has the same value as
314                                 `Thing.init` does, as it would need to be instantiated.
315                                 Assume overwrite?
316                              +/
317                             intoThis.tupleof[i] = meldThis.tupleof[i];
318                         }
319                         else
320                         {
321                             if (intoThis.tupleof[i] == Thing.init.tupleof[i])
322                             {
323                                 intoThis.tupleof[i] = meldThis.tupleof[i];
324                             }
325                         }
326                     }
327                     else
328                     {
329                         /+
330                             This is tricksy for bools. A value of false could be
331                             false, or merely unset. If we're not overwriting,
332                             let whichever side is true win out?
333                          +/
334                         static if (is(Thing == class))
335                         {
336                             if (intoThis.tupleof[i] == T.init)
337                             {
338                                 intoThis.tupleof[i] = meldThis.tupleof[i];
339                             }
340                         }
341                         else
342                         {
343                             if ((intoThis.tupleof[i] == T.init) ||
344                                 (intoThis.tupleof[i] == Thing.init.tupleof[i]))
345                             {
346                                 intoThis.tupleof[i] = meldThis.tupleof[i];
347                             }
348                         }
349                     }
350                 }
351             }
352             else
353             {
354                 /*import std.format : format;
355                 import std.traits : Unqual;
356 
357                 enum pattern = "`%s` `%s.%s` is not meldable!";
358                 enum message = pattern.format(
359                     Unqual!T.stringof,
360                     Unqual!QualThing.stringof,
361                     __traits(identifier, meldThis.tupleof[i]));
362                 static assert(0, message);*/
363             }
364         }
365     }
366 }
367 
368 ///
369 unittest
370 {
371     import std.conv : to;
372 
373     static struct TestFoo
374     {
375         string abc;
376         string def;
377         int i;
378         float f;
379         double d;
380         int[string] aa;
381         int[] arr;
382         int* ip;
383 
384         void blah() {}
385 
386         const string kek;
387         immutable bool bur;
388 
389         this(bool bur)
390         {
391             kek = "uden lo";
392             this.bur = bur;
393         }
394     }
395 
396     TestFoo f1; // = new TestFoo;
397     f1.abc = "ABC";
398     f1.def = "DEF";
399     f1.aa = [ "abc" : 123, "ghi" : 789 ];
400     f1.arr = [ 1, 0, 3, 0, 5 ];
401 
402     TestFoo f2; // = new TestFoo;
403     f2.abc = "this won't get copied";
404     f2.def = "neither will this";
405     f2.i = 42;
406     f2.f = 3.14f;
407     f2.aa = [ "abc" : 999, "def" : 456 ];
408     f2.arr = [ 0, 2, 0, 4 ];
409 
410     f2.meldInto(f1);
411 
412     with (f1)
413     {
414         import std.math : isNaN;
415 
416         assert((abc == "ABC"), abc);
417         assert((def == "DEF"), def);
418         assert((i == 42), i.to!string);
419         assert((f == 3.14f), f.to!string);
420         assert(d.isNaN, d.to!string);
421         assert((aa == [ "abc" : 123, "def" : 456, "ghi" : 789 ]), aa.to!string);
422         assert((arr == [ 1, 2, 3, 4, 5 ]), arr.to!string);
423     }
424 
425     TestFoo f3; // new TestFoo;
426     f3.abc = "abc";
427     f3.def = "def";
428     f3.i = 100_135;
429     f3.f = 99.9f;
430     f3.aa = [ "abc" : 123, "ghi" : 789 ];
431     f3.arr = [ 1, 0, 3, 0, 5 ];
432 
433     TestFoo f4; // new TestFoo;
434     f4.abc = "OVERWRITTEN";
435     f4.def = "OVERWRITTEN TOO";
436     f4.i = 0;
437     f4.f = 0.1f;
438     f4.d = 99.999;
439     f4.aa = [ "abc" : 999, "def" : 456 ];
440     f4.arr = [ 9, 2, 0, 4 ];
441 
442     f4.meldInto!(MeldingStrategy.aggressive)(f3);
443 
444     with (f3)
445     {
446         static if (__VERSION__ >= 2091)
447         {
448             import std.math : isClose;
449         }
450         else
451         {
452             import std.math : approxEqual;
453             alias isClose = approxEqual;
454         }
455 
456         assert((abc == "OVERWRITTEN"), abc);
457         assert((def == "OVERWRITTEN TOO"), def);
458         assert((i == 100_135), i.to!string); // 0 is int.init
459         assert((f == 0.1f), f.to!string);
460         assert(isClose(d, 99.999), d.to!string);
461         assert((aa == [ "abc" : 999, "def" : 456, "ghi" : 789 ]), aa.to!string);
462         assert((arr == [ 9, 2, 3, 4, 5 ]), arr.to!string);
463     }
464 
465     // Overwriting is just aggressive but always overwrites bools.
466 
467     struct User
468     {
469         enum Class { anyone, blacklist, whitelist, admin }
470         string nickname;
471         string alias_;
472         string ident;
473         string address;
474         string login;
475         bool special;
476         Class class_;
477     }
478 
479     User one;
480     with (one)
481     {
482         nickname = "foobar";
483         ident = "NaN";
484         address = "herpderp.net";
485         special = false;
486         class_ = User.Class.whitelist;
487     }
488 
489     User two;
490     with (two)
491     {
492         nickname = "foobar^";
493         alias_ = "FooBar";
494         address = "asdf.org";
495         login = "kamelusu";
496         special = true;
497         class_ = User.Class.blacklist;
498     }
499 
500     //import lu.conv : Enum;
501 
502     User twoCopy = two;
503 
504     one.meldInto!(MeldingStrategy.conservative)(two);
505     with (two)
506     {
507         assert((nickname == "foobar^"), nickname);
508         assert((alias_ == "FooBar"), alias_);
509         assert((ident == "NaN"), ident);
510         assert((address == "asdf.org"), address);
511         assert((login == "kamelusu"), login);
512         assert(special);
513         assert(class_ == User.Class.whitelist);//, Enum!(User.Class).toString(class_));
514     }
515 
516     one.class_ = User.Class.blacklist;
517 
518     one.meldInto!(MeldingStrategy.overwriting)(twoCopy);
519     with (twoCopy)
520     {
521         assert((nickname == "foobar"), nickname);
522         assert((alias_ == "FooBar"), alias_);
523         assert((ident == "NaN"), ident);
524         assert((address == "herpderp.net"), address);
525         assert((login == "kamelusu"), login);
526         assert(!special);
527         assert(class_ == User.Class.blacklist);//, Enum!(User.Class).toString(class_));
528     }
529 
530     struct EnumThing
531     {
532         enum Enum { unset, one, two, three }
533         Enum enum_;
534     }
535 
536     EnumThing e1;
537     EnumThing e2;
538     e2.enum_ = EnumThing.Enum.three;
539     assert(e1.enum_ == EnumThing.Enum.init);//, Enum!(EnumThing.Enum).toString(e1.enum_));
540     e2.meldInto(e1);
541     assert(e1.enum_ == EnumThing.Enum.three);//, Enum!(EnumThing.Enum).toString(e1.enum_));
542 
543     struct WithArray
544     {
545         string[] arr;
546     }
547 
548     WithArray w1, w2;
549     w1.arr = [ "arr", "matey", "I'ma" ];
550     w2.arr = [ "pirate", "stereotype", "unittest" ];
551     w2.meldInto(w1);
552     assert((w1.arr == [ "arr", "matey", "I'ma", "pirate", "stereotype", "unittest" ]), w1.arr.to!string);
553 
554     WithArray w3, w4;
555     w3.arr = [ "arr", "matey", "I'ma" ];
556     w4.arr = [ "arr", "matey", "I'ma" ];
557     w4.meldInto(w3);
558     assert((w3.arr == [ "arr", "matey", "I'ma" ]), w3.arr.to!string);
559 
560     struct Server
561     {
562         string address;
563     }
564 
565     struct Bot
566     {
567         string nickname;
568         Server server;
569     }
570 
571     Bot b1, b2;
572     b1.nickname = "foobar";
573     b1.server.address = "freenode.net";
574 
575     assert(!b2.nickname.length, b2.nickname);
576     assert(!b2.server.address.length, b2.nickname);
577     b1.meldInto(b2);
578     assert((b2.nickname == "foobar"), b2.nickname);
579     assert((b2.server.address == "freenode.net"), b2.server.address);
580 
581     b2.nickname = "harbl";
582     b2.server.address = "rizon.net";
583 
584     b2.meldInto!(MeldingStrategy.aggressive)(b1);
585     assert((b1.nickname == "harbl"), b1.nickname);
586     assert((b1.server.address == "rizon.net"), b1.server.address);
587 
588     class Class
589     {
590         static int i;
591         string s;
592         bool b;
593     }
594 
595     Class abc = new Class;
596     abc.i = 42;
597     abc.s = "some string";
598     abc.b = true;
599 
600     Class def = new Class;
601     def.s = "other string";
602     abc.meldInto(def);
603 
604     assert((def.i == 42), def.i.to!string);
605     assert((def.s == "other string"), def.s);
606     assert(def.b);
607 
608     abc.meldInto!(MeldingStrategy.aggressive)(def);
609     assert((def.s == "some string"), def.s);
610 
611     struct Bools
612     {
613         bool a = true;
614         bool b = false;
615     }
616 
617     Bools bools1, bools2, inverted, backupInverted;
618 
619     bools2.a = false;
620 
621     inverted.a = false;
622     inverted.b = true;
623     backupInverted = inverted;
624 
625     bools2.meldInto(bools1);
626     assert(!bools1.a);
627     assert(!bools1.b);
628 
629     bools2.meldInto(inverted);
630     assert(!inverted.a);
631     assert(inverted.b);
632     inverted = backupInverted;
633 
634     bools2.meldInto!(MeldingStrategy.overwriting)(inverted);
635     assert(!inverted.a);
636     assert(!inverted.b);
637     inverted = backupInverted;
638 
639     struct Asdf
640     {
641         string nickname = "sadf";
642         string server = "asdf.net";
643     }
644 
645     Asdf a, b;
646     a.server = "a";
647     b.server = "b";
648     b.meldInto!(MeldingStrategy.aggressive)(a);
649     assert((a.server == "b"), a.server);
650 
651     a.server = "a";
652     b.server = Asdf.init.server;
653     b.meldInto!(MeldingStrategy.aggressive)(a);
654     assert((a.server == "a"), a.server);
655 
656     struct Blah
657     {
658         int yes = 42;
659         @Unmeldable int no = 24;
660     }
661 
662     Blah blah1, blah2;
663     blah1.yes = 5;
664     blah1.no = 42;
665     blah1.meldInto!(MeldingStrategy.aggressive)(blah2);
666     assert((blah2.yes == 5), blah2.yes.to!string);
667     assert((blah2.no == 24), blah2.no.to!string);
668 }
669 
670 
671 // meldInto (array)
672 /++
673     Takes two arrays and melds them together, making a union of the two.
674 
675     It only overwrites members that are `T.init`, so only unset
676     fields get their values overwritten by the melding array. Supply a
677     template parameter [MeldingStrategy.aggressive] to make it overwrite if the
678     melding array's field is not `T.init`. Furthermore use
679     [MeldingStrategy.overwriting] if working with bool members.
680 
681     Example:
682     ---
683     int[] arr1 = [ 1, 2, 3, 0, 0, 0 ];
684     int[] arr2 = [ 0, 0, 0, 4, 5, 6 ];
685     arr1.meldInto!(MeldingStrategy.conservative)(arr2);
686 
687     assert(arr2 == [ 1, 2, 3, 4, 5, 6 ]);
688     ---
689 
690     Params:
691         strategy = To what extent the source object should overwrite set
692             (non-`init`) values in the receiving object.
693         meldThis = Array to meld (source).
694         intoThis = Reference to the array to meld (target).
695  +/
696 void meldInto(MeldingStrategy strategy = MeldingStrategy.conservative, Array1, Array2)
697     (auto ref Array1 meldThis, ref Array2 intoThis) pure nothrow
698 if (isMerelyArray!Array1 && isMerelyArray!Array2 && isMutable!Array2)
699 {
700     import std.traits : isDynamicArray, isStaticArray;
701 
702     static if (isDynamicArray!Array2)
703     {
704         if (!meldThis.length)
705         {
706             // Source empty, just return
707             return;
708         }
709         else if (!intoThis.length)
710         {
711             // Source has content but target empty, just inherit
712             intoThis = meldThis.dup;
713             return;
714         }
715 
716         // Ensure there's room for all elements
717         if (meldThis.length > intoThis.length) intoThis.length = meldThis.length;
718     }
719     else static if (isStaticArray!Array1 && isStaticArray!Array2)
720     {
721         static if (Array1.length == Array2.length)
722         {
723             if (meldThis == Array1.init)
724             {
725                 // Source empty, just return
726                 return;
727             }
728             else if (intoThis == Array2.init)
729             {
730                 // Source has content but target empty, just inherit
731                 intoThis = meldThis;  // value type, no need for .dup
732                 return;
733             }
734         }
735         else
736         {
737             import std.format : format;
738             static assert((Array2.length >= Array1.length),
739                 "Cannot meld a larger `%s` static array into a smaller `%s` static one"
740                 .format(Array1.stringof, Array2.stringof));
741         }
742     }
743     else static if (isDynamicArray!Array1 && isStaticArray!Array2)
744     {
745         assert((meldThis.length <= Array2.length),
746             "Cannot meld a larger dynamic array into a smaller static one");
747     }
748     else
749     {
750         import std.format : format;
751         static assert(0, "Attempted to meld an unsupported array type: `%s` into `%s`"
752             .format(Array1.stringof, Array2.stringof));
753     }
754 
755     foreach (immutable i, const val; meldThis)
756     {
757         static if (strategy == MeldingStrategy.conservative)
758         {
759             if ((val != typeof(val).init) && (intoThis[i] == typeof(intoThis[i]).init))
760             {
761                 intoThis[i] = val;
762             }
763         }
764         else static if (strategy == MeldingStrategy.aggressive)
765         {
766             if (val != typeof(val).init)
767             {
768                 intoThis[i] = val;
769             }
770         }
771         else static if (strategy == MeldingStrategy.overwriting)
772         {
773             intoThis[i] = val;
774         }
775         else
776         {
777             static assert(0, "Logic error; unexpected `MeldingStrategy` passed to array `meldInto`");
778         }
779     }
780 }
781 
782 ///
783 unittest
784 {
785     import std.conv : to;
786 
787     auto arr1 = [ 123, 0, 789, 0, 456, 0 ];
788     auto arr2 = [ 0, 456, 0, 123, 0, 789 ];
789     arr1.meldInto!(MeldingStrategy.conservative)(arr2);
790     assert((arr2 == [ 123, 456, 789, 123, 456, 789 ]), arr2.to!string);
791 
792     auto yarr1 = [ 'Z', char.init, 'Z', char.init, 'Z' ];
793     auto yarr2 = [ 'A', 'B', 'C', 'D', 'E', 'F' ];
794     yarr1.meldInto!(MeldingStrategy.aggressive)(yarr2);
795     assert((yarr2 == [ 'Z', 'B', 'Z', 'D', 'Z', 'F' ]), yarr2.to!string);
796 
797     auto harr1 = [ char.init, 'X' ];
798     yarr1.meldInto(harr1);
799     assert((harr1 == [ 'Z', 'X', 'Z', char.init, 'Z' ]), harr1.to!string);
800 
801     char[5] harr2 = [ '1', '2', '3', '4', '5' ];
802     char[] harr3;
803     harr2.meldInto(harr3);
804     assert((harr2 == harr3), harr3.to!string);
805 
806     int[3] asdf;
807     int[3] hasdf;
808     asdf.meldInto(hasdf);
809 
810     int[] dyn = new int[2];
811     int[3] stat;
812     dyn.meldInto(stat);
813 }
814 
815 
816 // meldInto
817 /++
818     Takes two associative arrays and melds them together, making a union of the two.
819 
820     This is largely the same as the array-version [meldInto] but doesn't need
821     the extensive template constraints it employs, so it might as well be kept separate.
822 
823     Example:
824     ---
825     int[string] aa1 = [ "abc" : 42, "def" : -1 ];
826     int[string] aa2 = [ "ghi" : 10, "jkl" : 7 ];
827     arr1.meldInto(arr2);
828 
829     assert("abc" in aa2);
830     assert("def" in aa2);
831     assert("ghi" in aa2);
832     assert("jkl" in aa2);
833     ---
834 
835     Params:
836         strategy = To what extent the source object should overwrite set
837             (non-`init`) values in the receiving object.
838         meldThis = Associative array to meld (source).
839         intoThis = Reference to the associative array to meld (target).
840  +/
841 void meldInto(MeldingStrategy strategy = MeldingStrategy.conservative, QualAA, AA)
842     (QualAA meldThis, ref AA intoThis) pure
843 if (isAssociativeArray!AA && is(QualAA : AA) && isMutable!AA)
844 {
845     if (!meldThis.length)
846     {
847         // Empty source
848         return;
849     }
850     else if (!intoThis.length)
851     {
852         // Empty target, just assign
853         intoThis = meldThis.dup;
854         return;
855     }
856 
857     foreach (immutable key, val; meldThis)
858     {
859         static if (strategy == MeldingStrategy.conservative)
860         {
861             if (val == typeof(val).init)
862             {
863                 // Source value is .init; do nothing
864                 continue;
865             }
866 
867             const target = key in intoThis;
868 
869             if (!target || (*target == typeof(*target).init))
870             {
871                 // Target value doesn't exist or is .init; meld
872                 intoThis[key] = val;
873             }
874         }
875         else static if ((strategy == MeldingStrategy.aggressive) ||
876             (strategy == MeldingStrategy.overwriting))
877         {
878             import std.traits : ValueType;
879 
880             static if ((strategy == MeldingStrategy.overwriting) &&
881                 is(ValueType!AA == bool))
882             {
883                 // Always overwrite
884                 intoThis[key] = val;
885             }
886             else
887             {
888                 if (val != typeof(val).init)
889                 {
890                     // Target value doesn't exist; meld
891                     intoThis[key] = val;
892                 }
893             }
894         }
895         else
896         {
897             static assert(0, "Logic error; unexpected `MeldingStrategy` passed to AA `meldInto`");
898         }
899     }
900 }
901 
902 ///
903 unittest
904 {
905     bool[string] aa1;
906     bool[string] aa2;
907 
908     aa1["a"] = true;
909     aa1["b"] = false;
910     aa2["c"] = true;
911     aa2["d"] = false;
912 
913     assert("a" in aa1);
914     assert("b" in aa1);
915     assert("c" in aa2);
916     assert("d" in aa2);
917 
918     aa1.meldInto!(MeldingStrategy.overwriting)(aa2);
919 
920     assert("a" in aa2);
921     assert("b" in aa2);
922 
923     string[string] saa1;
924     string[string] saa2;
925 
926     saa1["a"] = "a";
927     saa1["b"] = "b";
928     saa2["c"] = "c";
929     saa2["d"] = "d";
930 
931     saa1.meldInto!(MeldingStrategy.conservative)(saa2);
932     assert("a" in saa2);
933     assert("b" in saa2);
934 
935     saa1["a"] = "A";
936     saa1.meldInto!(MeldingStrategy.aggressive)(saa2);
937     assert(saa2["a"] == "A");
938 }