Skip to content

Commit b97f005

Browse files
authored
Merge pull request #4043 from John-Colvin/named
Add std.typecons.Tuple.rename
2 parents 76cd4bd + 657a0ff commit b97f005

File tree

1 file changed

+217
-9
lines changed

1 file changed

+217
-9
lines changed

std/typecons.d

Lines changed: 217 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)