Skip to content

Commit 95a9d9f

Browse files
jmdavisdlang-bot
authored andcommitted
Adds Instantiate, ApplyLeft, and ApplyRight to phobos.sys.meta.
This also makes it so that isImplicitlyConvertible and isQualifierConvertible in phobos.sys.traits can be partially instantiated, since I missed that before. Instantiate is basically identical to the one in std.meta implementation-wise, but the documentation and unit tests have been improved. ApplyLeft and ApplyRight should be functionally the same as their std.meta counterparts, but they have a simpler implementation than their std.meta counterparts (I'm pretty sure that the extra complexity in the old implementation is to work around built-in types not working with aliases, which was fixed a while ago). Of course, their documentation and tests have also been improved.
1 parent aeace12 commit 95a9d9f

File tree

2 files changed

+384
-1
lines changed

2 files changed

+384
-1
lines changed

phobos/sys/meta.d

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
$(LREF any)
6868
$(LREF indexOf)
6969
))
70+
$(TR $(TD Template instantiation) $(TD
71+
$(LREF ApplyLeft)
72+
$(LREF ApplyRight)
73+
$(LREF Instantiate)
74+
))
7075
)
7176
7277
References:
@@ -672,3 +677,327 @@ unittest
672677
static assert(indexOf!(isSameType!string, int, int, int, string) == 3);
673678
static assert(indexOf!(isSameType!string, int, int, int, int) == -1);
674679
}
680+
681+
/++
682+
Instantiates the given template with the given arguments and evaluates to
683+
the result of that template.
684+
685+
This is used to work around some syntactic limitations that D has with
686+
regards to instantiating templates. Essentially, D requires a name for a
687+
template when instantiating it (be it the name of the template itself or an
688+
alias to the template), which causes problems when you don't have that.
689+
690+
Specifically, if the template is within an $(LREF AliasSeq) - e.g.
691+
$(D Templates[0]!Args) - or it's the result of another template - e.g
692+
$(D Foo!Bar!Baz) - the instantiation is illegal. This leaves two ways to
693+
solve the problem. The first is to create an alias, e.g.
694+
---
695+
alias Template = Templates[0];
696+
enum result = Template!Args;
697+
698+
alias Partial = Foo!Bar;
699+
alias T = Partial!Baz;
700+
---
701+
The second is to use Instantiate, e.g.
702+
---
703+
enum result = Instantiate!(Templates[0], Args);
704+
705+
alias T = Instiantiate!(Foo!Bar, Baz);
706+
---
707+
708+
Of course, the downside to this is that it adds an additional template
709+
instantiation, but it avoids creating an alias just to be able to
710+
instantiate a template. So, whether it makes sense to use Instantiate
711+
instead of an alias naturally depends on the situation, but without it,
712+
we'd be forced to create aliases even in situations where that's
713+
problematic.
714+
715+
See_Also:
716+
$(LREF ApplyLeft)
717+
$(LREF ApplyRight)
718+
+/
719+
alias Instantiate(alias Template, Args...) = Template!Args;
720+
721+
///
722+
@safe unittest
723+
{
724+
import phobos.sys.traits : ConstOf, isImplicitlyConvertible, isSameType, isInteger;
725+
726+
alias Templates = AliasSeq!(isImplicitlyConvertible!int,
727+
isSameType!string,
728+
isInteger,
729+
ConstOf);
730+
731+
// Templates[0]!long does not compile, because the compiler can't parse it.
732+
733+
static assert( Instantiate!(Templates[0], long));
734+
static assert(!Instantiate!(Templates[0], string));
735+
736+
static assert(!Instantiate!(Templates[1], long));
737+
static assert( Instantiate!(Templates[1], string));
738+
739+
static assert( Instantiate!(Templates[2], long));
740+
static assert(!Instantiate!(Templates[2], string));
741+
742+
static assert(is(Instantiate!(Templates[3], int) == const int));
743+
static assert(is(Instantiate!(Templates[3], double) == const double));
744+
}
745+
746+
///
747+
@safe unittest
748+
{
749+
template hasMember(string member)
750+
{
751+
enum hasMember(T) = __traits(hasMember, T, member);
752+
}
753+
754+
struct S
755+
{
756+
int foo;
757+
}
758+
759+
// hasMember!"foo"!S does not compile,
760+
// because having multiple ! arguments is not allowed.
761+
762+
static assert( Instantiate!(hasMember!"foo", S));
763+
static assert(!Instantiate!(hasMember!"bar", S));
764+
}
765+
766+
/++
767+
Instantiate also allows us to do template instantations via templates that
768+
take other templates as arguments.
769+
+/
770+
@safe unittest
771+
{
772+
import phobos.sys.traits : isInteger, isNumeric, isUnsignedInteger;
773+
774+
alias Results = Map!(ApplyRight!(Instantiate, int),
775+
isInteger, isNumeric, isUnsignedInteger);
776+
777+
static assert([Results] == [true, true, false]);
778+
}
779+
780+
/++
781+
ApplyLeft does a
782+
$(LINK2 http://en.wikipedia.org/wiki/Partial_application, partial application)
783+
of its arguments, providing a way to bind a set of arguments to the given
784+
template while delaying actually instantiating that template until the full
785+
set of arguments is provided. The "Left" in the name indicates that the
786+
initial arguments are one the left-hand side of the argument list
787+
when the given template is instantiated.
788+
789+
Essentially, ApplyLeft results in a template that stores Template and Args,
790+
and when that intermediate template is instantiated in turn, it instantiates
791+
Template with Args on the left-hand side of the arguments to Template and
792+
with the arguments to the intermediate template on the right-hand side -
793+
i.e. Args is applied to the left when instantiating Template.
794+
795+
So, if you have
796+
---
797+
alias Intermediate = ApplyLeft!(MyTemplate, Arg1, Arg2);
798+
alias Result = Intermediate!(ArgA, ArgB);
799+
---
800+
then that is equivalent to
801+
---
802+
alias Result = MyTemplate!(Arg1, Arg2, ArgA, ArgB);
803+
---
804+
with the difference being that you have an intermediate template which can
805+
be stored or passed to other templates (e.g. as a template predicate).
806+
807+
The only difference between ApplyLeft and $(LREF ApplyRight) is whether
808+
Args is on the left-hand or the right-hand side of the arguments given to
809+
Template when it's instantiated.
810+
811+
Note that in many cases, the need for ApplyLeft can be eliminated by making
812+
it so that Template can be partially instantiated. E.G.
813+
---
814+
enum isSameType(T, U) = is(T == U);
815+
816+
template isSameType(T)
817+
{
818+
enum isSameType(U) = is(T == U);
819+
}
820+
---
821+
makes it so that both of these work
822+
---
823+
enum result1 = isSameType!(int, long);
824+
825+
alias Intermediate = isSameType!int;
826+
enum result2 = Intermediate!long;
827+
---
828+
whereas if only the two argument version is provided, then ApplyLeft would
829+
be required for the second use case.
830+
---
831+
enum result1 = isSameType!(int, long);
832+
833+
alias Intermediate = ApplyLeft!(isSameType, int);
834+
enum result2 = Intermediate!long;
835+
---
836+
837+
See_Also:
838+
$(LREF ApplyRight)
839+
$(LREF Instantiate)
840+
+/
841+
template ApplyLeft(alias Template, Args...)
842+
{
843+
alias ApplyLeft(Right...) = Template!(Args, Right);
844+
}
845+
846+
///
847+
@safe unittest
848+
{
849+
{
850+
alias Intermediate = ApplyLeft!(AliasSeq, ubyte, ushort, uint);
851+
alias Result = Intermediate!(char, wchar, dchar);
852+
static assert(is(Result == AliasSeq!(ubyte, ushort, uint, char, wchar, dchar)));
853+
}
854+
{
855+
enum isImplicitlyConvertible(T, U) = is(T : U);
856+
857+
// i.e. isImplicitlyConvertible!(ubyte, T) is what all is checking for
858+
// with each element in the AliasSeq.
859+
static assert(all!(ApplyLeft!(isImplicitlyConvertible, ubyte),
860+
short, ushort, int, uint, long, ulong));
861+
}
862+
{
863+
enum hasMember(T, string member) = __traits(hasMember, T, member);
864+
865+
struct S
866+
{
867+
bool foo;
868+
int bar;
869+
string baz;
870+
}
871+
872+
static assert(all!(ApplyLeft!(hasMember, S), "foo", "bar", "baz"));
873+
}
874+
{
875+
// Either set of arguments can be empty, since the first set is just
876+
// stored to be applied later, and then when the intermediate template
877+
// is instantiated, they're all applied to the given template in the
878+
// requested order. However, whether the code compiles when
879+
// instantiating the intermediate template depends on what kinds of
880+
// arguments the given template requires.
881+
882+
alias Intermediate1 = ApplyLeft!AliasSeq;
883+
static assert(Intermediate1!().length == 0);
884+
885+
enum isSameSize(T, U) = T.sizeof == U.sizeof;
886+
887+
alias Intermediate2 = ApplyLeft!(isSameSize, int);
888+
static assert(Intermediate2!uint);
889+
890+
alias Intermediate3 = ApplyLeft!(isSameSize, int, uint);
891+
static assert(Intermediate3!());
892+
893+
alias Intermediate4 = ApplyLeft!(isSameSize);
894+
static assert(Intermediate4!(int, uint));
895+
896+
// isSameSize requires two arguments
897+
alias Intermediate5 = ApplyLeft!isSameSize;
898+
static assert(!__traits(compiles, Intermediate5!()));
899+
static assert(!__traits(compiles, Intermediate5!int));
900+
static assert(!__traits(compiles, Intermediate5!(int, long, string)));
901+
}
902+
}
903+
904+
/++
905+
ApplyRight does a
906+
$(LINK2 http://en.wikipedia.org/wiki/Partial_application, partial application)
907+
of its arguments, providing a way to bind a set of arguments to the given
908+
template while delaying actually instantiating that template until the full
909+
set of arguments is provided. The "Right" in the name indicates that the
910+
initial arguments are one the right-hand side of the argument list
911+
when the given template is instantiated.
912+
913+
Essentially, ApplyRight results in a template that stores Template and
914+
Args, and when that intermediate template is instantiated in turn, it
915+
instantiates Template with the arguments to the intermediate template on
916+
the left-hand side and with Args on the right-hand side - i.e. Args is
917+
applied to the right when instantiating Template.
918+
919+
So, if you have
920+
---
921+
alias Intermediate = ApplyRight!(MyTemplate, Arg1, Arg2);
922+
alias Result = Intermediate!(ArgA, ArgB);
923+
---
924+
then that is equivalent to
925+
---
926+
alias Result = MyTemplate!(ArgA, ArgB, Arg1, Arg2);
927+
---
928+
with the difference being that you have an intermediate template which can
929+
be stored or passed to other templates (e.g. as a template predicate).
930+
931+
The only difference between $(LREF ApplyLeft) and ApplyRight is whether
932+
Args is on the left-hand or the right-hand side of the arguments given to
933+
Template when it's instantiated.
934+
935+
See_Also:
936+
$(LREF ApplyLeft)
937+
$(LREF Instantiate)
938+
+/
939+
template ApplyRight(alias Template, Args...)
940+
{
941+
alias ApplyRight(Left...) = Template!(Left, Args);
942+
}
943+
944+
///
945+
@safe unittest
946+
{
947+
{
948+
alias Intermediate = ApplyRight!(AliasSeq, ubyte, ushort, uint);
949+
alias Result = Intermediate!(char, wchar, dchar);
950+
static assert(is(Result == AliasSeq!(char, wchar, dchar, ubyte, ushort, uint)));
951+
}
952+
{
953+
enum isImplicitlyConvertible(T, U) = is(T : U);
954+
955+
// i.e. isImplicitlyConvertible!(T, short) is what Filter is checking
956+
// for with each element in the AliasSeq.
957+
static assert(is(Filter!(ApplyRight!(isImplicitlyConvertible, short),
958+
ubyte, string, short, float, int) ==
959+
AliasSeq!(ubyte, short)));
960+
}
961+
{
962+
enum hasMember(T, string member) = __traits(hasMember, T, member);
963+
964+
struct S1
965+
{
966+
bool foo;
967+
}
968+
969+
struct S2
970+
{
971+
int foo() { return 42; }
972+
}
973+
974+
static assert(all!(ApplyRight!(hasMember, "foo"), S1, S2));
975+
}
976+
{
977+
// Either set of arguments can be empty, since the first set is just
978+
// stored to be applied later, and then when the intermediate template
979+
// is instantiated, they're all applied to the given template in the
980+
// requested order. However, whether the code compiles when
981+
// instantiating the intermediate template depends on what kinds of
982+
// arguments the given template requires.
983+
984+
alias Intermediate1 = ApplyRight!AliasSeq;
985+
static assert(Intermediate1!().length == 0);
986+
987+
enum isSameSize(T, U) = T.sizeof == U.sizeof;
988+
989+
alias Intermediate2 = ApplyRight!(isSameSize, int);
990+
static assert(Intermediate2!uint);
991+
992+
alias Intermediate3 = ApplyRight!(isSameSize, int, uint);
993+
static assert(Intermediate3!());
994+
995+
alias Intermediate4 = ApplyRight!(isSameSize);
996+
static assert(Intermediate4!(int, uint));
997+
998+
// isSameSize requires two arguments
999+
alias Intermediate5 = ApplyRight!isSameSize;
1000+
static assert(!__traits(compiles, Intermediate5!()));
1001+
static assert(!__traits(compiles, Intermediate5!int));
1002+
}
1003+
}

0 commit comments

Comments
 (0)