tuples - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Discord (community-run)
  - Forums
  - IRC
  - Wiki
  - Issues
  - Contributing
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
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 a local clone.

# tuples

Tuples are for combining multiple values to be used as a single object. They are implemented as a library feature by the `Tuple` template from the `std.typecons` module.

`Tuple` makes use of `AliasSeq` from the `std.meta` module for some of its operations.

This chapter covers only the more common operations of tuples. For more information on tuples and templates see Philippe Sigaud's D Templates: A Tutorial.

##### `Tuple` and `tuple()`

Tuples are usually constructed by the convenience function `tuple()`:

    import std.stdio;
    import std.typecons;

    void main() {
        auto t = tuple(42, "hello");
        writeln(t);
    }

The `tuple` call above constructs an object that consists of the `int` value 42 and the `string` value `"hello"`. The output of the program includes the type of the tuple object and its members:

    Tuple!(int, string)(42, "hello")

The tuple type above is the equivalent of the following pseudo `struct` definition and likely have been implemented in exactly the same way:

    // The equivalent of Tuple!(int, string)
    struct __Tuple_int_string {
        int __member_0;
        string __member_1;
    }

The members of a tuple are normally accessed by their index values. That syntax suggests that tuples can be seen as arrays consisting of different types of elements:

        writeln(t[0]);
        writeln(t[1]);

The output:

    42
    hello

###### Member properties

It is possible to access the members by properties if the tuple is constructed directly by the `Tuple` template instead of the `tuple()` function. The type and the name of each member are specified as two consecutive template parameters:

        auto t = Tuple!(int, "number",
                        string, "message")(42, "hello");

The definition above allows accessing the members by `.number` and `.message` properties as well:

        writeln("by index 0 : ", t[0]);
        writeln("by .number : ", t.number);
        writeln("by index 1 : ", t[1]);
        writeln("by .message: ", t.message);

The output:

    by index 0 : 42
    by .number : 42
    by index 1 : hello
    by .message: hello

###### Expanding the members as a list of values

Tuple members can be expanded as a list of values that can be used e.g. as an argument list when calling a function. The members can be expanded either by the `.expand` property or by slicing:

    import std.stdio;
    import std.typecons;

    void foo(int i, string s, double d, char c) {
        // ...
    }

    void bar(int i, double d, char c) {
        // ...
    }

    void main() {
        auto t = tuple(1, "2", 3.3, '4');

        // Both of the following lines are equivalents of
        // foo(1, "2", 3.3, '4'):
        foo(t.expand);
        foo(t[]);

        // The equivalent of bar(1, 3.3, '4'):
        bar(t[0], t[$-2..$]);
    }

The tuple above consists of four values of `int`, `string`, `double`, and `char`. Since those types match the parameter list of `foo()`, an expansion of its members can be used as arguments to `foo()`. When calling `bar()`, a matching argument list is made up of the first member and the last two members of the tuple.

As long as the members are compatible to be elements of the same array, the expansion of a tuple can be used as the element values of an array literal as well:

    import std.stdio;
    import std.typecons;

    void main() {
        auto t = tuple(1, 2, 3);
        auto a = [ t.expand, t[] ];
        writeln(a);
    }

The array literal above is initialized by expanding the same tuple twice:

    [1, 2, 3, 1, 2, 3]

###### Compile-time `foreach`

Because their values can be expanded, tuples can be used with the `foreach` statement as well:

        auto t = tuple(42, "hello", 1.5);

        foreach (i, member; t) {
            writefln("%s: %s", i, member);
        }

The output:

    0: 42
    1: hello
    2: 1.5

The `foreach` statement above may give a false impression: It may be thought of being a loop that gets executed at run time. That is not the case. Rather, a `foreach` statement that operates on the members of a tuple is an unrolling of the loop body for each member. The `foreach` statement above is the equivalent of the following code:

        {
            enum size_t i = 0;
            int member = t[i];
            writefln("%s: %s", i, member);
        }
        {
            enum size_t i = 1;
            string member = t[i];
            writefln("%s: %s", i, member);
        }
        {
            enum size_t i = 2;
            double member = t[i];
            writefln("%s: %s", i, member);
        }

The reason for the unrolling is the fact that when the tuple members are of different types, the `foreach` body has to be compiled differently for each type.

We will see `static foreach`, a more powerful loop unrolling feature, in a later chapter.

###### Returning multiple values from functions

Tuples can be a simple solution to the limitation of functions having to return a single value. An example of this is `std.algorithm.findSplit`. `findSplit()` searches for a range inside another range and produces a result consisting of three pieces: the part before the found range, the found range, and the part after the found range:

    import std.algorithm;

    // ...

        auto entireRange = "hello";
        auto searched = "ll";

        auto result = findSplit(entireRange, searched);

        writeln("before: ", result[0]);
        writeln("found : ", result[1]);
        writeln("after : ", result[2]);

The output:

    before: he
    found : ll
    after : o

Another option for returning multiple values from a function is to return a `struct` object:

    struct Result {
        // ...
    }

    Result foo() {
        // ...
    }

##### `AliasSeq`

`AliasSeq` is defined in the `std.meta` module. It is used for representing a concept that is normally used by the compiler but otherwise not available to the programmer as an entity: A comma-separated list of values, types, and symbols (i.e. `alias` template arguments). The following are three examples of such lists:

- Function argument list
- Template argument list
- Array literal element list

The following three lines of code are examples of those lists in the same order:

        foo(1, "hello", 2.5);         // function arguments
        auto o = Bar!(char, long)();  // template arguments
        auto a = [ 1, 2, 3, 4 ];      // array literal elements

`Tuple` takes advantage of `AliasSeq` when expanding its members.

The name `AliasSeq` comes from "alias sequence" and it can contain types, values, and symbols. (`AliasSeq` and `std.meta` used to be called `TypeTuple` and `std.typetuple`, respectively.)

This chapter includes `AliasSeq` examples that consist only of types or only of values. Examples of its use with both types and values will appear in the next chapter. `AliasSeq` is especially useful with variadic templates, which we will see in the next chapter as well.

###### `AliasSeq` consisting of values

The values that an `AliasSeq` represents are specified as its template arguments.

Let's imagine a function that takes three parameters:

    import std.stdio;

    void foo(int i, string s, double d) {
        writefln("foo is called with %s, %s, and %s.", i, s, d);
    }

That function would normally be called with three arguments:

        foo(1, "hello", 2.5);

`AliasSeq` can combine those arguments as a single entity and can automatically be expanded when calling functions:

    import std.meta;

    // ...

        alias arguments = AliasSeq!(1, "hello", 2.5);
        foo(arguments);

Although it looks like the function is now being called with a single argument, the `foo()` call above is the equivalent of the previous one. As a result, both calls produce the same output:

    foo is called with 1, hello, and 2.5.

Also note that `arguments` is not defined as a variable, e.g. with `auto`. Rather, it is an `alias` of a specific `AliasSeq` instance. Although it is possible to define variables of `AliasSeq`s as well, the examples in this chapter will use them only as aliases.

As we have seen above with `Tuple`, when the values are compatible to be elements of the same array, an `AliasSeq` can be used to initialize an array literal as well:

        alias elements = AliasSeq!(1, 2, 3, 4);
        auto arr = [ elements ];
        assert(arr == [ 1, 2, 3, 4 ]);

###### Indexing and slicing

Same with `Tuple`, the members of an `AliasSeq` can be accessed by indexes and slices:

        alias arguments = AliasSeq!(1, "hello", 2.5);
        assert(arguments[0] == 1);
        assert(arguments[1] == "hello");
        assert(arguments[2] == 2.5);

Let's assume there is a function with parameters matching the last two members of the `AliasSeq` above. That function can be called with a slice of just the last two members of the `AliasSeq`:

    void bar(string s, double d) {
        // ...
    }

    // ...

        bar(arguments[$-2 .. $]);

###### `AliasSeq` consisting of types

Members of an `AliasSeq` can consist of types. In other words, not a specific value of a specific type but a type like `int` itself. An `AliasSeq` consisting of types can represent template arguments.

Let's use an `AliasSeq` with a `struct` template that has two parameters. The first parameter of this template determines the element type of a member array and the second parameter determines the return value of a member function:

    import std.conv;

    struct S(ElementT, ResultT) {
        ElementT[] arr;

        ResultT length() {
            return to!ResultT(arr.length);
        }
    }

    void main() {
        auto s = S!(double, int)([ 1, 2, 3 ]);
        auto l = s.length();
    }

In the code above, we see that the template is instantiated with `(double, int)`. An `AliasSeq` can represent the same argument list as well:

    import std.meta;

    // ...

        alias Types = AliasSeq!(double, int);
        auto s = S!Types([ 1, 2, 3 ]);

Although it appears to be a single template argument, `Types` gets expanded automatically and the template instantiation becomes `S!(double, int)` as before.

`AliasSeq` is especially useful in variadic templates. We will see examples of this in the next chapter.

###### `foreach` with `AliasSeq`

Same with `Tuple`, the `foreach` statement operating on an `AliasSeq` is not a run time loop. Rather, it is the unrolling of the loop body for each member.

Let's see an example of this with a unit test written for the `S` struct that was defined above. The following code tests `S` for element types `int`, `long`, and `float` (`ResultT` is always `size_t` in this example):

    unittest {
        alias Types = AliasSeq!(int, long, float);

        foreach (Type; Types) {
            auto s = S!(Type, size_t)([ Type.init, Type.init ]);
            assert(s.length() == 2);
        }
    }

The `foreach` variable `Type` corresponds to `int`, `long`, and `float`, in that order. As a result, the `foreach` statement gets compiled as the equivalent of the code below:

        {
            auto s = S!(int, size_t)([ int.init, int.init ]);
            assert(s.length() == 2);
        }
        {
            auto s = S!(long, size_t)([ long.init, long.init ]);
            assert(s.length() == 2);
        }
        {
            auto s = S!(float, size_t)([ float.init, float.init ]);
            assert(s.length() == 2);
        }

##### `.tupleof` property

`.tupleof` represents the members of a type or an object. When applied to a user-defined type, `.tupleof` provides access to the definitions of the members of that type:

    import std.stdio;

    struct S {
        int number;
        string message;
        double value;
    }

    void main() {
        foreach (i, MemberType; typeof(S.tupleof)) {
            writefln("Member %s:", i);
            writefln("  type: %s", MemberType.stringof);

            string name = S.tupleof[i].stringof;
            writefln("  name: %s", name);
        }
    }

`S.tupleof` appears in two places in the program. First, the types of the elements are obtained by applying `typeof` to `.tupleof` so that each type appears as the `MemberType` variable. Second, the name of the member is obtained by `S.tupleof[i].stringof`.

    Member 0:
      type: int
      name: number
    Member 1:
      type: string
      name: message
    Member 2:
      type: double
      name: value

`.tupleof` can be applied to an object as well. In that case, it produces a tuple consisting of the values of the members of the object:

        auto object = S(42, "hello", 1.5);

        foreach (i, member; object.tupleof) {
            writefln("Member %s:", i);
            writefln("  type : %s", typeof(member).stringof);
            writefln("  value: %s", member);
        }

The `foreach` variable `member` represents each member of the object:

    Member 0:
      type : int
      value: 42
    Member 1:
      type : string
      value: hello
    Member 2:
      type : double
      value: 1.5

Here, an important point to make is that the tuple that `.tupleof` returns consists of the members of the object themselves, not their copies. In other words, the tuple members are references to the actual object members.

##### Summary

- `tuple()` combines different types of values similar to a `struct` object.
- Explicit use of `Tuple` allows accessing the members by properties.
- The members can be expanded as a value list by `.expand` or by slicing.
- `foreach` with a tuple is not a run time loop; rather, it is a loop unrolling.
- `AliasSeq` represents concepts like function argument list, template argument list, array literal element list, etc.
- `AliasSeq` can consist of values and types.
- Tuples support indexing and slicing.
- `.tupleof` provides information about the members of types and objects.

macros: TITLE=Tuples DESCRIPTION=Combining values and types to be able to access them as members of the same object. KEYWORDS=d programming language tutorial book Tuple AliasSeq tuple
Copyright © 1999-2026 by the D Language Foundation | Page generated by Ddoc on Mon Aug 17 18:57:13 2026