@@ -464,7 +464,7 @@ template Tuple(Specs...)
464464 alias Types = staticMap! (extractType, fieldSpecs);
465465
466466 // /
467- @safe unittest
467+ static if (Specs.length == 0 ) @safe unittest
468468 {
469469 alias Fields = Tuple ! (int , " id" , string , float );
470470 static assert (is (Fields.Types == AliasSeq! (int , string , float )));
@@ -476,7 +476,7 @@ template Tuple(Specs...)
476476 alias fieldNames = staticMap! (extractName, fieldSpecs);
477477
478478 // /
479- @safe unittest
479+ static if (Specs.length == 0 ) @safe unittest
480480 {
481481 alias Fields = Tuple ! (int , " id" , string , float );
482482 static assert (Fields.fieldNames == AliasSeq! (" id" , " " , " " ));
@@ -492,7 +492,7 @@ template Tuple(Specs...)
492492 mixin (injectNamedFields());
493493
494494 // /
495- @safe unittest
495+ static if (Specs.length == 0 ) @safe unittest
496496 {
497497 auto t1 = tuple(1 , " hello " , 2.3 );
498498 assert (t1.toString() == ` Tuple!(int, string, double)(1, " hello ", 2.3)` );
@@ -550,7 +550,7 @@ template Tuple(Specs...)
550550 }
551551
552552 // /
553- @safe unittest
553+ static if (Specs.length == 0 ) @safe unittest
554554 {
555555 alias ISD = Tuple ! (int , string , double );
556556 auto tup = ISD (1 , " test" , 3.2 );
@@ -574,7 +574,7 @@ template Tuple(Specs...)
574574 }
575575
576576 // /
577- @safe unittest
577+ static if (Specs.length == 0 ) @safe unittest
578578 {
579579 int [2 ] ints;
580580 Tuple ! (int , int ) t = ints;
@@ -597,7 +597,7 @@ template Tuple(Specs...)
597597 }
598598
599599 // /
600- @safe unittest
600+ static if (Specs.length == 0 ) @safe unittest
601601 {
602602 alias IntVec = Tuple ! (int , int , int );
603603 alias DubVec = Tuple ! (double , double , double );
@@ -644,7 +644,7 @@ template Tuple(Specs...)
644644 }
645645
646646 // /
647- @safe unittest
647+ static if (Specs.length == 0 ) @safe unittest
648648 {
649649 Tuple ! (int , string ) t1 = tuple(1 , " test" );
650650 Tuple ! (double , string ) t2 = tuple(1.0 , " test" );
@@ -700,7 +700,7 @@ template Tuple(Specs...)
700700 The first `v1` for which `v1 > v2` is true determines
701701 the result. This could lead to unexpected behaviour.
702702 */
703- @safe unittest
703+ static if (Specs.length == 0 ) @safe unittest
704704 {
705705 auto tup1 = tuple(1 , 1 , 1 );
706706 auto tup2 = tuple(1 , 100 , 100 );
@@ -744,6 +744,208 @@ template Tuple(Specs...)
744744 }
745745 }
746746
747+ /**
748+ * Renames the elements of a $(LREF Tuple).
749+ *
750+ * `rename` uses the passed `names` and returns a new
751+ * $(LREF Tuple) using these names, with the content
752+ * unchanged.
753+ * If fewer names are passed than there are members
754+ * of the $(LREF Tuple) then those trailing members are unchanged.
755+ * An empty string will remove the name for that member.
756+ * It is an compile-time error to pass more names than
757+ * there are members of the $(LREF Tuple).
758+ */
759+ ref rename (names... )()
760+ if (names.length == 0 || allSatisfy! (isSomeString, typeof (names)))
761+ {
762+ import std.algorithm : equal;
763+ // to circumvent bug 16418
764+ static if (names.length == 0 || equal([names], [fieldNames]))
765+ return this ;
766+ else
767+ {
768+ enum nT = Types.length;
769+ enum nN = names.length;
770+ static assert (nN <= nT, " Cannot have more names than tuple members" );
771+ alias allNames = AliasSeq! (names, fieldNames[nN .. $]);
772+
773+ template GetItem (size_t idx)
774+ {
775+ import std.array : empty;
776+ static if (idx < nT)
777+ alias GetItem = Alias! (Types[idx]);
778+ else static if (allNames[idx - nT].empty)
779+ alias GetItem = AliasSeq! ();
780+ else
781+ alias GetItem = Alias! (allNames[idx - nT]);
782+ }
783+
784+ import std.range : roundRobin, iota;
785+ alias NewTupleT = Tuple ! (staticMap! (GetItem, aliasSeqOf! (
786+ roundRobin(iota(nT), iota (nT, 2 * nT)))));
787+ return * (() @trusted => cast (NewTupleT* )&this )();
788+ }
789+ }
790+
791+ // /
792+ static if (Specs.length == 0 ) @safe unittest
793+ {
794+ auto t0 = tuple(4 , " hello" );
795+
796+ auto t0Named = t0.rename! (" val" , " tag" );
797+ assert (t0Named.val == 4 );
798+ assert (t0Named.tag == " hello" );
799+
800+ Tuple ! (float , " dat" , size_t [2 ], " pos" ) t1;
801+ t1.pos = [2 , 1 ];
802+ auto t1Named = t1.rename! " height" ;
803+ t1Named.height = 3.4f ;
804+ assert (t1Named.height == 3.4f );
805+ assert (t1Named.pos == [2 , 1 ]);
806+ t1Named.rename! " altitude" .altitude = 5 ;
807+ assert (t1Named.height == 5 );
808+
809+ Tuple ! (int , " a" , int , int , " c" ) t2;
810+ t2 = tuple(3 ,4 ,5 );
811+ auto t2Named = t2.rename! (" " , " b" );
812+ // "a" no longer has a name
813+ static assert (! hasMember! (typeof (t2Named), " a" ));
814+ assert (t2Named[0 ] == 3 );
815+ assert (t2Named.b == 4 );
816+ assert (t2Named.c == 5 );
817+
818+ // not allowed to specify more names than the tuple has members
819+ static assert (! __traits(compiles, t2.rename! (" a" ," b" ," c" ," d" )));
820+
821+ // use it in a range pipeline
822+ import std.range : iota, zip;
823+ import std.algorithm : map, sum;
824+ auto res = zip(iota(1 , 4 ), iota(10 , 13 ))
825+ .map! (t => t.rename! (" a" , " b" ))
826+ .map! (t => t.a * t.b)
827+ .sum;
828+ assert (res == 68 );
829+ }
830+
831+ /**
832+ * Overload of $(LREF _rename) that takes an associative array
833+ * `translate` as a template parameter, where the keys are
834+ * either the names or indices of the members to be changed
835+ * and the new names are the corresponding values.
836+ * Every key in `translate` must be the name of a member of the
837+ * $(LREF tuple).
838+ * The same rules for empty strings apply as for the variadic
839+ * template overload of $(LREF _rename).
840+ */
841+ ref rename (alias translate)()
842+ if (is (typeof (translate) : V[K], V, K) && isSomeString! V &&
843+ (isSomeString! K || is (K : size_t )))
844+ {
845+ import std.range : ElementType;
846+ static if (isSomeString! (ElementType! (typeof (translate.keys ))))
847+ {
848+ {
849+ import std.conv : to;
850+ import std.algorithm : filter, canFind;
851+ enum notFound = translate.keys
852+ .filter! (k => fieldNames.canFind(k) == - 1 );
853+ static assert (notFound.empty, " Cannot find members "
854+ ~ notFound.to! string ~ " in type "
855+ ~ typeof (this ).stringof);
856+ }
857+ return this .rename! (aliasSeqOf! (
858+ {
859+ import std.array : empty;
860+ auto names = [fieldNames];
861+ foreach (ref n; names)
862+ if (! n.empty)
863+ if (auto p = n in translate)
864+ n = * p;
865+ return names;
866+ }()));
867+ }
868+ else
869+ {
870+ {
871+ import std.algorithm : filter;
872+ import std.conv : to;
873+ enum invalid = translate.keys .
874+ filter! (k => k < 0 || k >= this .length);
875+ static assert (invalid.empty, " Indices " ~ invalid.to! string
876+ ~ " are out of bounds for tuple with length "
877+ ~ this .length.to! string );
878+ }
879+ return this .rename! (aliasSeqOf! (
880+ {
881+ auto names = [fieldNames];
882+ foreach (k, v; translate)
883+ names[k] = v;
884+ return names;
885+ }()));
886+ }
887+ }
888+
889+ // /
890+ static if (Specs.length == 0 ) @safe unittest
891+ {
892+ // replacing names by their current name
893+
894+ Tuple ! (float , " dat" , size_t [2 ], " pos" ) t1;
895+ t1.pos = [2 , 1 ];
896+ auto t1Named = t1.rename! ([" dat" : " height" ]);
897+ t1Named.height = 3.4 ;
898+ assert (t1Named.pos == [2 , 1 ]);
899+ t1Named.rename! ([" height" : " altitude" ]).altitude = 5 ;
900+ assert (t1Named.height == 5 );
901+
902+ Tuple ! (int , " a" , int , " b" ) t2;
903+ t2 = tuple(3 , 4 );
904+ auto t2Named = t2.rename! ([" a" : " b" , " b" : " c" ]);
905+ assert (t2Named.b == 3 );
906+ assert (t2Named.c == 4 );
907+ }
908+
909+ // /
910+ static if (Specs.length == 0 ) @safe unittest
911+ {
912+ // replace names by their position
913+
914+ Tuple ! (float , " dat" , size_t [2 ], " pos" ) t1;
915+ t1.pos = [2 , 1 ];
916+ auto t1Named = t1.rename! ([0 : " height" ]);
917+ t1Named.height = 3.4 ;
918+ assert (t1Named.pos == [2 , 1 ]);
919+ t1Named.rename! ([0 : " altitude" ]).altitude = 5 ;
920+ assert (t1Named.height == 5 );
921+
922+ Tuple ! (int , " a" , int , " b" , int , " c" ) t2;
923+ t2 = tuple(3 , 4 , 5 );
924+ auto t2Named = t2.rename! ([0 : " c" , 2 : " a" ]);
925+ assert (t2Named.a == 5 );
926+ assert (t2Named.b == 4 );
927+ assert (t2Named.c == 3 );
928+ }
929+
930+ static if (Specs.length == 0 ) @safe unittest
931+ {
932+ // check that empty translations work fine
933+ enum string [string ] a0 = null ;
934+ enum string [int ] a1 = null ;
935+ Tuple ! (float , " a" , float , " b" ) t0;
936+
937+ auto t1 = t0.rename! a0;
938+
939+ t1.a = 3 ;
940+ t1.b = 4 ;
941+ auto t2 = t0.rename! a1;
942+ t2.a = 3 ;
943+ t2.b = 4 ;
944+ auto t3 = t0.rename;
945+ t3.a = 3 ;
946+ t3.b = 4 ;
947+ }
948+
747949 /**
748950 * Takes a slice of this `Tuple`.
749951 *
@@ -764,7 +966,7 @@ template Tuple(Specs...)
764966 }
765967
766968 // /
767- @safe unittest
969+ static if (Specs.length == 0 ) @safe unittest
768970 {
769971 Tuple ! (int , string , float , double ) a;
770972 a[1 ] = " abc" ;
@@ -1020,6 +1222,12 @@ private template ReverseTupleSpecs(T...)
10201222 }
10211223}
10221224
1225+ // ensure that internal Tuple unittests are compiled
1226+ unittest
1227+ {
1228+ Tuple ! () t;
1229+ }
1230+
10231231@safe unittest
10241232{
10251233 import std.conv ;
0 commit comments