ApplyLeft

ApplyLeft does a partial application of its arguments, providing a way to bind a set of arguments to the given template while delaying actually instantiating that template until the full set of arguments is provided. The "Left" in the name indicates that the initial arguments are one the left-hand side of the argument list when the given template is instantiated.

Essentially, ApplyLeft results in a template that stores Template and Args, and when that intermediate template is instantiated in turn, it instantiates Template with Args on the left-hand side of the arguments to Template and with the arguments to the intermediate template on the right-hand side - i.e. Args is applied to the left when instantiating Template.

So, if you have

alias Intermediate = ApplyLeft!(MyTemplate, Arg1, Arg2);
alias Result = Intermediate!(ArgA, ArgB);

then that is equivalent to

alias Result = MyTemplate!(Arg1, Arg2, ArgA, ArgB);

with the difference being that you have an intermediate template which can be stored or passed to other templates (e.g. as a template predicate).

The only difference between ApplyLeft and ApplyRight is whether Args is on the left-hand or the right-hand side of the arguments given to Template when it's instantiated.

Note that in many cases, the need for ApplyLeft can be eliminated by making it so that Template can be partially instantiated. E.G.

enum isSameType(T, U) = is(T == U);

template isSameType(T)
{
    enum isSameType(U) = is(T == U);
}

makes it so that both of these work

enum result1 = isSameType!(int, long);

alias Intermediate = isSameType!int;
enum result2 = Intermediate!long;

whereas if only the two argument version is provided, then ApplyLeft would be required for the second use case.

enum result1 = isSameType!(int, long);

alias Intermediate = ApplyLeft!(isSameType, int);
enum result2 = Intermediate!long;
template ApplyLeft (
alias Template
Args...
) {}

Examples

{
    alias Intermediate = ApplyLeft!(AliasSeq, ubyte, ushort, uint);
    alias Result = Intermediate!(char, wchar, dchar);
    static assert(is(Result == AliasSeq!(ubyte, ushort, uint, char, wchar, dchar)));
}
{
    enum isImplicitlyConvertible(T, U) = is(T : U);

    // i.e. isImplicitlyConvertible!(ubyte, T) is what all is checking for
    // with each element in the AliasSeq.
    static assert(all!(ApplyLeft!(isImplicitlyConvertible, ubyte),
                       short, ushort, int, uint, long, ulong));
}
{
    enum hasMember(T, string member) = __traits(hasMember, T, member);

    struct S
    {
        bool foo;
        int bar;
        string baz;
    }

    static assert(all!(ApplyLeft!(hasMember, S), "foo", "bar", "baz"));
}
{
    // Either set of arguments can be empty, since the first set is just
    // stored to be applied later, and then when the intermediate template
    // is instantiated, they're all applied to the given template in the
    // requested order. However, whether the code compiles when
    // instantiating the intermediate template depends on what kinds of
    // arguments the given template requires.

    alias Intermediate1 = ApplyLeft!AliasSeq;
    static assert(Intermediate1!().length == 0);

    enum isSameSize(T, U) = T.sizeof == U.sizeof;

    alias Intermediate2 = ApplyLeft!(isSameSize, int);
    static assert(Intermediate2!uint);

    alias Intermediate3 = ApplyLeft!(isSameSize, int, uint);
    static assert(Intermediate3!());

    alias Intermediate4 = ApplyLeft!(isSameSize);
    static assert(Intermediate4!(int, uint));

    // isSameSize requires two arguments
    alias Intermediate5 = ApplyLeft!isSameSize;
    static assert(!__traits(compiles, Intermediate5!()));
    static assert(!__traits(compiles, Intermediate5!int));
    static assert(!__traits(compiles, Intermediate5!(int, long, string)));
}

See Also

Meta