Im­prove this page Quickly fork, edit on­line, and sub­mit a pull re­quest for this page. Re­quires a signed-in GitHub ac­count. This works well for small changes. If you'd like to make larger changes you may want to con­sider using local clone. Page wiki View or edit the com­mu­nity-main­tained wiki page as­so­ci­ated with this page.

Tu­ples

A tuple is a se­quence of el­e­ments. Those el­e­ments can be types, ex­pres­sions, or aliases. The num­ber and el­e­ments of a tuple are fixed at com­pile time; they can­not be changed at run time.

Tu­ples have char­ac­ter­is­tics of both structs and ar­rays. Like structs, the tuple el­e­ments can be of dif­fer­ent types. Like ar­rays, the el­e­ments can be ac­cessed via in­dex­ing.

So how does one con­struct a tuple? There isn't a spe­cific tuple lit­eral syn­tax. But since vari­adic tem­plate pa­ra­me­ters cre­ate tu­ples, we can de­fine a tem­plate to cre­ate one:

template Tuple(E...)
{
    alias E Tuple;
}

and it's used like:

Tuple!(int, long, float)	// create a tuple of 3 types
Tuple!(3, 7, 'c')		// create a tuple of 3 expressions
Tuple!(int, 8)			// create a tuple of a type and an expression

In order to sym­bol­i­cally refer to a tuple, use an alias:

alias Tuple!(float, float, 3) TP; // TP is now a tuple of two floats and 3

Tu­ples can be used as ar­gu­ments to tem­plates, and if so they are ‘flat­tened’ out into a list of ar­gu­ments. This makes it straight­for­ward to ap­pend a new el­e­ment to an ex­ist­ing tuple or con­cate­nate tu­ples:

alias Tuple!(TP, 8) TR;  // TR is now float,float,3,8
alias Tuple!(TP, TP) TS; // TS is float,float,3,float,float,3

Tu­ples share many char­ac­ter­is­tics with ar­rays. For starters, the num­ber of el­e­ments in a tuple can be re­trieved with the .length prop­erty:

TP.length	// evaluates to 3

Tu­ples can be in­dexed:

TP[1] f = TP[2];	// f is declared as a float and initialized to 3

and even sliced:

alias TP[0..length-1] TQ; // TQ is now the same as Tuple!(float, float)

Yes, length is de­fined within the [ ]s. There is one re­stric­tion: the in­dices for in­dex­ing and slic­ing must be eval­u­at­able at com­pile time.

void foo(int i)
{
    TQ[i] x;		// error, i is not constant
}

These make it sim­ple to pro­duce the ‘head’ and ‘tail’ of a tuple. The head is just TP[0], the tail is TP[1 .. length]. Given the head and tail, mix with a lit­tle con­di­tional com­pi­la­tion, and we can im­ple­ment some clas­sic re­cur­sive al­go­rithms with tem­plates. For ex­am­ple, this tem­plate re­turns a tuple con­sist­ing of the trail­ing type ar­gu­ments TL with the first oc­cur­rence of the first type ar­gu­ment T re­moved:

template Erase(T, TL...)
{
    static if (TL.length == 0)
	// 0 length tuple, return self
        alias TL Erase;
    else static if (is(T == TL[0]))
	// match with first in tuple, return tail
        alias TL[1 .. length] Erase;
    else
	// no match, return head concatenated with recursive tail operation
        alias Tuple!(TL[0], Erase!(T, TL[1 .. length])) Erase;
}

Type Tu­ples

If a tuple's el­e­ments are solely types, it is called a Type­Tu­ple (some­times called a type list). Since func­tion pa­ra­me­ter lists are a list of types, a type tuple can be re­trieved from them. One way is using an Is­Ex­pres­sion:

int foo(int x, long y);

...
static if (is(foo P == function))
    alias P TP;
// TP is now the same as Tuple!(int, long)

This is gen­er­al­ized in the tem­plate std.​traits.Pa­ra­me­ter­Type­Tu­ple:

import std.traits;

...
alias ParameterTypeTuple!(foo) TP;	// TP is the tuple (int, long)

Type­Tu­ples can be used to de­clare a func­tion:

float bar(TP);	// same as float bar(int, long)

If im­plicit func­tion tem­plate in­stan­ti­a­tion is being done, the type tuple rep­re­sent­ing the pa­ra­me­ter types can be de­duced:

int foo(int x, long y);

void Bar(R, P...)(R function(P))
{
    writeln("return type is ", typeid(R));
    writeln("parameter types are ", typeid(P));
}

...
Bar(&foo);

Prints:

return type is int
parameter types are (int,long)

Type de­duc­tion can be used to cre­ate a func­tion that takes an ar­bi­trary num­ber and type of ar­gu­ments:

void Abc(P...)(P p)
{
    writeln("parameter types are ", typeid(P));
}

Abc(3, 7L, 6.8);

Prints:

parameter types are (int,long,double)

For a more com­pre­hen­sive treat­ment of this as­pect, see Vari­adic Tem­plates.

Ex­pres­sion Tu­ples

If a tuple's el­e­ments are solely ex­pres­sions, it is called an Ex­pres­sion­Tu­ple. The Tuple tem­plate can be used to cre­ate one:

alias Tuple!(3, 7L, 6.8) ET;

...
writeln(ET);            // prints 376.8
writeln(ET[1]);         // prints 7
writeln(ET[1..length]); // prints 76.8

It can be used to cre­ate an array lit­eral:

alias Tuple!(3, 7, 6) AT;

...
int[] a = [AT];		// same as [3,7,6]

The data fields of a struct or class can be turned into an ex­pres­sion tuple using the .tu­pleof prop­erty:

struct S { int x; long y; }

void foo(int a, long b)
{
    writeln(a, b);
}

...
S s;
s.x = 7;
s.y = 8;
foo(s.x, s.y);	// prints 78
foo(s.tupleof);	// prints 78
s.tupleof[1] = 9;
s.tupleof[0] = 10;
foo(s.tupleof); // prints 109
s.tupleof[2] = 11;  // error, no third field of S

A type tuple can be cre­ated from the data fields of a struct using typeof:

writeln(typeid(typeof(S.tupleof)));	// prints (int,long)

This is en­cap­su­lated in the tem­plate std.​traits.Field­Type­Tu­ple.

Loop­ing

While the head-tail style of func­tional pro­gram­ming works with tu­ples, it's often more con­ve­nient to use a loop. The Fore­ach­State­ment can loop over ei­ther Type­Tu­ples or Ex­pres­sion­Tu­ples.

alias Tuple!(int, long, float) TL;
foreach (i, T; TL)
    writefln("TL[%d] = %s", i, typeid(T));

alias Tuple!(3, 7L, 6.8) ET;
foreach (i, E; ET)
    writefln("ET[%d] = %s", i, E);

Prints:

TL[0] = int
TL[1] = long
TL[2] = float
ET[0] = 3
ET[1] = 7
ET[2] = 6.8

Tuple De­c­la­ra­tions

A vari­able de­clared with a Type­Tu­ple be­comes an Ex­pres­sion­Tu­ple:

alias Tuple!(int, long) TL;

void foo(TL tl)
{
    writeln(tl, tl[1]);
}

foo(1, 6L);	// prints 166

Putting It All To­gether

These ca­pa­bil­i­ties can be put to­gether to im­ple­ment a tem­plate that will en­cap­su­late all the ar­gu­ments to a func­tion, and re­turn a del­e­gate that will call the func­tion with those ar­gu­ments.

import std.stdio;

R delegate() CurryAll(Dummy=void, R, U...)(R function(U) dg, U args)
{
    struct Foo
    {
	typeof(dg) dg_m;
	U args_m;

	R bar()
	{
	    return dg_m(args_m);
	}
    }

    Foo* f = new Foo;
    f.dg_m = dg;
    foreach (i, arg; args)
	f.args_m[i] = arg;
    return &f.bar;
}

R delegate() CurryAll(R, U...)(R delegate(U) dg, U args)
{
    struct Foo
    {
	typeof(dg) dg_m;
	U args_m;

	R bar()
	{
	    return dg_m(args_m);
	}
    }

    Foo* f = new Foo;
    f.dg_m = dg;
    foreach (i, arg; args)
	f.args_m[i] = arg;
    return &f.bar;
}


void main()
{
    static int plus(int x, int y, int z)
    {
	return x + y + z;
    }

    auto plus_two = CurryAll(&plus, 2, 3, 4);
    writefln("%d", plus_two());
    assert(plus_two() == 9);

    int minus(int x, int y, int z)
    {
	return x + y + z;
    }

    auto minus_two = CurryAll(&minus, 7, 8, 9);
    writefln("%d", minus_two());
    assert(minus_two() == 24);
}

The rea­son for the Dummy pa­ra­me­ter is that one can­not over­load two tem­plates with the same pa­ra­me­ter list. So we make them dif­fer­ent by giv­ing one a dummy pa­ra­me­ter.

Fu­ture Di­rec­tions

Forums | Comments | Search | Downloads | Home