Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Forums
  - IRC
  - Community Discord
  - Wiki
  - GitHub
  - Issues
  - Get involved
  - 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.

# Compile-time Sequences

Contents

1. Background
2. AliasSeq
3. Available operations
   1. Checking the length
   2. Indexing and slicing
   3. Assignment
   4. Loops
4. Auto-expansion
5. Homogenous sequences
   1. Type sequences
   2. Value sequences
   3. Symbol sequences

## Background

Compile-time sequences are an important metaprogramming concept that comes naturally from D support for variadic templates. They allow a programmer to operate on types, symbols and values, enabling the ability to define compile-time algorithms that operate on types, symbols and values.

Note: For historical reasons these sequences can sometimes be called tuples in documentation or compiler internals, but don't get confused - they don't have much in common with tuples that commonly exist in other languages. Sequences of values of different types that can be returned from functions are provided by std.typecons.Tuple. Using the term "tuple" to mean compile-time sequences is discouraged to avoid confusion, and if encountered should result in a documentation bug report.

Consider this simple variadic template:

    template Variadic(T...) { /* ... */ }

T here is a TemplateParameterSequence, which is a core language feature. It has its own special semantics, and, from the programmer's point of view, is most similar to an array of compile-time entities - types, symbols (names) and values. One can check the length of this sequence and access any individual element:

    template Variadic(T...)
    {
        static assert (T.length > 1);
        pragma(msg, T[0]);
        pragma(msg, T[1]);
    }

    alias dummy = Variadic!(int, 42);
    // prints during compilation:
    // int
    // 42

## AliasSeq

The language itself does not provide any means to define such sequences outside of a template parameter declaration. Instead, there is a simple utility provided by the standard library:

    alias AliasSeq(T...) = T;

All it does is give a specific variadic argument sequence an externally accessible name so that it can be worked with in any other context:

    import std.meta;
    // can alias to some other name
    alias Name = AliasSeq!(int, 42);
    pragma(msg, Name[0]);   // int
    pragma(msg, Name[1]);   // 42
    // or work with a sequence directly
    pragma(msg, AliasSeq!("aaa", 0, double)[2]);    // double

## Available operations

### Checking the length

    import std.meta;
    static assert (AliasSeq!(1, 2, 3, 4).length == 4);

### Indexing and slicing

Indexes must be known at compile-time

    import std.meta;
    alias Numbers = AliasSeq!(1, 2, 3, 4);
    static assert (Numbers[1] == 2);
    alias SubNumbers = Numbers[1 .. $];
    static assert (SubNumbers[0] == 2);

### Assignment

Works only if the sequence element is a symbol that refers to a mutable variable

    import std.meta;

    void main()
    {
        int x;
        alias seq = AliasSeq!(10, x);
        seq[1] = 42;
        assert (x == 42);
        // seq[0] = 42; // won't compile, can't assign to a constant
    }

### Loops

D's foreach statement has special semantics when iterating over compile-time sequences. It repeats the body of the loop for each of the sequence elements, with the loop iteration symbol being an alias for each compile-time sequence element in turn.

    import std.meta;

    void main()
    {
        foreach (sym; AliasSeq!(int, "literal", main))
        {
            static if (is(sym))
                pragma (msg, sym);
            else
                pragma (msg, typeof(sym));
        }
    }

    /* Prints:
    int
    string
    void()
    */

Note: Static foreach should be preferred in new code.

## Auto-expansion

One less obvious property of compile-time argument sequences is that when used as an argument to a function or template, they are automatically treated as a sequence of comma-separated arguments:

    import std.meta;

    template Print0(T...)
    {
        pragma(msg, T[0]);
    }

    alias Dummy = Print0!(AliasSeq!(int, double));

This will only print int during compilation because the last line gets rewritten as alias Dummy = Print0!(int, double). If auto-expansion didn't happen, AliasSeq!(int, double) would be printed instead. This is an inherent part of the language semantics for variadic sequences, and thus also preserved by AliasSeq.

## Homogenous sequences

An AliasSeq that consists of only type or value elements are commonly called "type sequences" or "value sequences" respectively. The concept of a "symbol sequence" is used less frequently but fits the same pattern.

### Type sequences

It is possible to use homogenous type sequences in declarations:

    import std.meta;
    alias Params = AliasSeq!(int, double, string);
    void foo(Params); // void foo(int, double, string);

#### Type sequence instantiation

D supports a special variable declaration syntax where a type sequence acts as a type:

    import std.meta;

    void foo()
    {
        AliasSeq!(int, double, string) variables;
        variables[0] = 42;
        variables[1] = 42.0;
        variables[2] = "just a normal variable";
    }

The compiler will rewrite such a declaration to something like this:

    int __var1;
    double __var2;
    string __var3;
    alias variables = AliasSeq!(__var1, __var2, __var3);

So a type sequence instance is a kind of symbol sequence that only aliases variables, known as an lvalue sequence.

Variadic template functions use a type sequence instance for a function parameter:

    void foo(T...)(T args)
    {
        // 'T' is a type sequence of argument types.
        // 'args' is an instance of T whose elements are the function parameters
        static assert(is(typeof(args) == T));
        args[0] = T[0].init;
    }

Note: std.typecons.Tuple wraps a type sequence instance, preventing auto-expansion, and allowing it to be returned from functions.

### Value sequences

It is possible to use a sequence of values of the same type to declare an array literal:

    import std.meta;
    enum e = 3;
    enum arr = [ AliasSeq!(1, 2, e) ];
    static assert(arr == [ 1, 2, 3 ]);

The above sequence is an rvalue sequence, as it is comprised only of literal values and manifest constants. Each element's value is known at compile-time.

The following demonstrates use of an lvalue sequence:

    import std.meta, std.algorithm;
    auto x = 3, y = 7;
    alias pair = AliasSeq!(x, y);
    swap(pair);
    assert(x == 7 && y == 3);

As above, such sequences may use assignment.

#### Aggregate field sequences

.tupleof is a class/struct instance property that provides an lvalue sequence of each field.

### Symbol sequences

A symbol sequence aliases any named symbol - types, variables, functions and templates - but not literals. Like an alias sequence, the kind of elements can be mixed.

Copyright © 1999-2021 by the D Language Foundation | Page generated by Ddoc on Thu Feb 18 11:53:28 2021