Improve this page Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using local clone. Page wiki View or edit the community-maintained wiki page associated with this page.

Tuples

A tuple is a sequence of elements. Those elements can be types, expressions, or aliases. The number and elements of a tuple are fixed at compile time; they cannot be changed at run time.

Tuples have characteristics of both structs and arrays. Like structs, the tuple elements can be of different types. Like arrays, the elements can be accessed via indexing.

So how does one construct a tuple? There isn't a specific tuple literal syntax. But since variadic template parameters create tuples, we can define a template to create one:

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

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 symbolically refer to a tuple, use an alias:

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

Tuples can be used as arguments to templates, and if so they are ‘flattened’ out into a list of arguments. This makes it straightforward to append a new element to an existing tuple or concatenate tuples:

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

Tuples share many characteristics with arrays. For starters, the number of elements in a tuple can be retrieved with the .length property:

TP.length	// evaluates to 3

Tuples can be indexed:

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

and even sliced:

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

There is one restriction: the indices for indexing and slicing must be evaluatable at compile time. When used within [ ]s $ is defined as the length of the tuple being indexed or sliced.

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

These make it simple to produce the ‘head’ and ‘tail’ of a tuple. The head is just TP[0], the tail is TP[1 .. $]. Given the head and tail, mix with a little conditional compilation, and we can implement some classic recursive algorithms with templates. For example, this template returns a tuple consisting of the trailing type arguments TL with the first occurrence of the first type argument T removed:

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

Type Tuples

If a tuple's elements are solely types, it is called a TypeTuple (sometimes called a type list). Since function parameter lists are a list of types, a type tuple can be retrieved from them. One way is using an IsExpression:

int foo(int x, long y);

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

This is generalized in the template std.traits.ParameterTypeTuple:

import std.traits;

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

TypeTuples can be used to declare a function:

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

If implicit function template instantiation is being done, the type tuple representing the parameter types can be deduced:

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 deduction can be used to create a function that takes an arbitrary number and type of arguments:

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 comprehensive treatment of this aspect, see Variadic Templates.

Expression Tuples

If a tuple's elements are solely expressions, it is called an ExpressionTuple. The Tuple template can be used to create one:

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

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

It can be used to create an array literal:

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

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

The data fields of a struct or class can be turned into an expression tuple using the .tupleof property:

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 created from the data fields of a struct using typeof:

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

This is encapsulated in the template std.traits.FieldTypeTuple.

Looping

While the head-tail style of functional programming works with tuples, it's often more convenient to use a loop. The ForeachStatement can loop over either TypeTuples or ExpressionTuples.

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

alias ET = Tuple!(3, 7L, 6.8);
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 Declarations

A variable declared with a TypeTuple becomes an ExpressionTuple:

alias TL = Tuple!(int, long);

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

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

Putting It All Together

These capabilities can be put together to implement a template that will encapsulate all the arguments to a function, and return a delegate that will call the function with those arguments.

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 reason for the Dummy parameter is that one cannot overload two templates with the same parameter list. So we make them different by giving one a dummy parameter.

Future Directions

Forums | Comments | Search | Downloads | Home